/* $Header: /afs/athena.mit.edu/astaff/project/atdev/src/plotter/RCS/Axis.c,v 3.7 94/09/23 12:15:57 dot Exp Locker: dot $ */

/*******************************************************************
  Copyright (C) 1990 by the Massachusetts Institute of Technology

   Export of this software from the United States of America is assumed
   to require a specific license from the United States Government.
   It is the responsibility of any person or organization contemplating
   export to obtain such a license before exporting.

WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
distribute this software and its documentation for any purpose and
without fee is hereby granted, provided that the above copyright
notice appear in all copies and that both that copyright notice and
this permission notice appear in supporting documentation, and that
the name of M.I.T. not be used in advertising or publicity pertaining
to distribution of the software without specific, written prior
permission.  M.I.T. makes no representations about the suitability of
this software for any purpose.  It is provided "as is" without express
or implied warranty.

***************************************************************** */

#include <math.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xlibint.h>	/* for max, min macros */

#ifdef _AtDevelopment_
#include "Scale.h"
#include "AtConverters.h"
#include "FontFamily.h"
#include "Text.h" 
#include "AxisP.h"
#else
#include <At/Scale.h>
#include <At/AtConverters.h>
#include <At/FontFamily.h>
#include <At/Text.h>
#include <At/AxisP.h>
#endif

static double zero = 0.0;
static double one = 1.0;
    
#define offset(field) XtOffset(AtAxisObject,field)
static XtResource axis_resources[] = {
  {XtNlabel, XtCLabel, XtRString,
     sizeof(String), offset(axis.label), XtRString, NULL},
  {XtNlabelFontFamily, XtCFontFamily, XtRString,
     sizeof(String), offset(axis.labelFontFamily),
     XtRString, "new century schoolbook"},
  {XtNnumberFontFamily, XtCFontFamily, XtRString,
     sizeof(String), offset(axis.numberFontFamily),
     XtRString, "new century schoolbook"},
  {XtNlabelSize, XtCFontSize, XtRFontSize,
     sizeof(int), offset(axis.labelSize),
     XtRImmediate, (caddr_t) AtFontNORMAL},
  {XtNnumberSize, XtCFontSize, XtRFontSize,
     sizeof(int), offset(axis.numberSize),
     XtRImmediate, (caddr_t) AtFontMEDIUM},
  {XtNnumberStyle, XtCFontStyle, XtRFontStyle,
     sizeof(int), offset(axis.numberStyle),
     XtRImmediate, (caddr_t) AtFontPLAIN},
  {XtNaxisColor, XtCForeground, XtRPixel,
     sizeof(Pixel), offset(axis.axisColor),
     XtRString, (caddr_t) XtDefaultForeground},
  {XtNnumberColor, XtCForeground, XtRPixel,
     sizeof(Pixel), offset(axis.numberColor),
     XtRString, (caddr_t) XtDefaultForeground},
  {XtNlabelColor, XtCForeground, XtRPixel,
     sizeof(Pixel), offset(axis.labelColor),
     XtRString, (caddr_t) XtDefaultForeground},
  {XtNmin, XtCMin, XtRDouble,
     sizeof(double), offset(axis.axis_min), XtRDouble, (caddr_t)&zero},
  {XtNmax, XtCMax, XtRDouble,
     sizeof(double), offset(axis.axis_max), XtRDouble, (caddr_t)&one},
  {XtNtransform, XtCTransform, XtRTransform,
     sizeof(short), offset(axis.transform),
     XtRImmediate, (caddr_t)AtTransformLINEAR},
  {XtNvertical, XtCVertical, XtRBoolean,
     sizeof(Boolean), offset(axis.vertical), XtRImmediate, (caddr_t)True},
  {XtNmirror, XtCMirror, XtRBoolean,
     sizeof(Boolean), offset(axis.mirror), XtRImmediate, (caddr_t)False},
  {XtNdrawNumbers, XtCDrawNumbers, XtRBoolean,
     sizeof(Boolean), offset(axis.drawNumbers), XtRImmediate, (caddr_t)True},
  {XtNdrawGrid, XtCDrawGrid, XtRBoolean,
     sizeof(Boolean), offset(axis.drawGrid), XtRImmediate, (caddr_t)False},
  {XtNautoNumber, XtCAutoNumber, XtRBoolean,
     sizeof(Boolean), offset(axis.autoNumber), XtRImmediate, (caddr_t)True},
  {XtNticInterval, XtCTicInterval, XtRDouble,
     sizeof(double), offset(axis.ticInterval), XtRDouble,(caddr_t)&one},
  {XtNsubtics, XtCSubtics, XtRShort,
     sizeof(short), offset(axis.subtics), XtRImmediate, (caddr_t)0},
  {XtNticDensity, XtCTicDensity, XtRShort,
     sizeof(short), offset(axis.ticDensity), XtRImmediate, (caddr_t)0},
  {XtNsubticDensity, XtCSubticDensity, XtRShort,
     sizeof(short), offset(axis.subticDensity), XtRImmediate,(caddr_t)0},
  {XtNticMultiplier, XtCTicMultiplier, XtRDouble,
     sizeof(double), offset(axis.ticMultiplier), XtRDouble, (caddr_t)&one},
  {XtNticFormat, XtCTicFormat, XtRString,
     sizeof(String), offset(axis.ticFormat), XtRString, (caddr_t)"%.3lg"},
  {XtNticsOutside, XtCTicsOutside, XtRBoolean,
     sizeof(Boolean), offset(axis.ticsOutside), XtRImmediate, (caddr_t)True},
  {XtNticsInside, XtCTicsInside, XtRBoolean,
     sizeof(Boolean), offset(axis.ticsInside), XtRImmediate, (caddr_t)False},
  {XtNnumbersOutside, XtCNumbersOutside, XtRBoolean,
     sizeof(Boolean), offset(axis.numbersOutside), XtRImmediate, (caddr_t)True},
  {XtNticLength, XtCTicLength, XtRShort,
     sizeof(short), offset(axis.ticLength), XtRImmediate, (caddr_t)5},
  {XtNsubticLength, XtCTicLength, XtRShort,
     sizeof(short), offset(axis.subticLength), XtRImmediate, (caddr_t)2},
  {XtNaxisNeedsRedraw, XtCaxisNeedsRedraw, XtRCallback,
     sizeof(XtCallbackList), offset(axis.axisNeedsRedraw), XtRCallback, NULL},
  {XtNerrorCallback, XtCErrorCallback, XtRCallback,
     sizeof(XtCallbackList), offset(axis.errorCallback), XtRCallback, NULL},
  {XtNticLabels, XtCTicLabels, XtRString,
     sizeof(String), offset(axis.ticLabels), XtRImmediate, (caddr_t) NULL},
};

#undef offset

static Boolean AxisSetValues(AtAxisObject, AtAxisObject, AtAxisObject);
static void AxisClassInitialize(void);
static void AxisInitialize(Widget, Widget);
static void AxisDestroy(Widget);
static void RecalcNumTics(AtAxisObject);
static void CheckScaleValue(AtAxisObject);
static void AxisError(AtAxisObject, char *);

AtAxisClassRec atAxisClassRec = {
  {
    /* superclass         */    (WidgetClass) &objectClassRec,
    /* class_name         */    "AtAxis",
    /* widget_size        */    sizeof(AtAxisRec),
    /* class_initialize   */    AxisClassInitialize,
    /* class_part_initialize*/  NULL,
    /* class_inited       */    FALSE,
    /* initialize         */    (XtInitProc) AxisInitialize,
    /* initialize_hook    */    NULL,
    /* pad                */    NULL,
    /* pad                */    NULL,
    /* pad                */    0,
    /* resources          */    axis_resources,
    /* num_resources      */    XtNumber(axis_resources),
    /* xrm_class          */    NULLQUARK,
    /* pad                */    FALSE,
    /* pad                */    FALSE,
    /* pad                */    FALSE,
    /* pad                */    FALSE,
    /* destroy            */    (XtWidgetProc) AxisDestroy,
    /* pad                */    NULL,
    /* pad                */    NULL,
    /* set_values         */    (XtSetValuesFunc) AxisSetValues,
    /* set_values_hook    */    NULL,
    /* pad                */    NULL,
    /* get_values_hook    */    NULL,
    /* pad                */    NULL,
    /* version            */    XtVersion,
    /* callback_offsets   */    NULL,
    /* pad                */    NULL,
    /* pad                */    NULL,
    /* pad                */    NULL,
    /* extension            */  NULL
},
/* AtAxisClassPart initialization */
{
    0 
}
};

WidgetClass atAxisObjectClass = (WidgetClass) &atAxisClassRec;

#define AtNewString(s) ((s) != NULL ? XtNewString((s)) : NULL)
#define CopyLabel(a) a->axis.label = (a->axis.label != NULL ? \
                                     XtNewString(a->axis.label) : NULL);
#define FreeLabel(a) XtFree(a->axis.label)

/**********************************************************************/

static void GetLabelText(AtAxisObject a)
{
  if ((a->axis.label != NULL) && (*a->axis.label != '\0')) {
    a->axis.labelText = AtTextCreate(a->axis.label, a->axis.labelFF,
				     a->axis.labelSize);
    if (a->axis.vertical == True) AtTextRotate(a->axis.labelText);
  }
  else
    a->axis.labelText = NULL;
}

/**********************************************************************/

static void FreeLabelText(AtAxisObject a)
{
  if (a->axis.labelText)
    AtTextDestroy(a->axis.labelText);
}

/**********************************************************************/

static void GetAxisGC(AtAxisObject a)
{
  XGCValues gcv;
  gcv.foreground = a->axis.axisColor;
  a->axis.axisGC = XtGetGC(XtParent((Widget)a), GCForeground, &gcv);
}

/**********************************************************************/

static void GetDottedGC(AtAxisObject a)
{
  XGCValues gcv;
  gcv.foreground = a->axis.axisColor;
  gcv.line_style = LineOnOffDash;
  gcv.dashes = (char)2;
  a->axis.dottedGC = XtGetGC(XtParent((Widget)a),
			     GCForeground | GCLineStyle | GCDashList,
			     &gcv);
}    

/**********************************************************************/

static void GetLabelGC(AtAxisObject a)
{
  XGCValues gcv;
    
  gcv.foreground = a->axis.labelColor;
  a->axis.labelGC = XtGetGC(XtParent((Widget)a), GCForeground, &gcv);
}

/**********************************************************************/

static void GetNumberGC(AtAxisObject a)
{
  XGCValues gcv;

  gcv.foreground = a->axis.numberColor;
  gcv.font = a->axis.numberFont->fid;
  a->axis.numberGC = XtGetGC(XtParent((Widget)a),
			     GCForeground | GCFont, &gcv);
}

/**********************************************************************/

static void ParseTicLabels(AtAxisObject a)

{
  char *start, *cur;
  int nslots = 0, len;

  /* Parse the ticLabels resource into an internal array of Strings */

  a->axis.nuserticlabels = 0;
  a->axis.userticlabels = NULL;
  cur = start = a->axis.ticLabels;

  if (cur == NULL || *cur == '\0')  return;

  while (1)  {
    if ((*cur == '\n') || (*cur == '\0'))  {
      if (a->axis.nuserticlabels >= nslots)  {
	a->axis.userticlabels = (String *) XtRealloc(a->axis.userticlabels, 
					     (nslots + 10) * sizeof(String));
	nslots += 10;
      }
      len = cur - start;
      a->axis.userticlabels[a->axis.nuserticlabels] = 
	(String) XtMalloc((len+1) * sizeof(char));
#ifdef SYSV
      memcpy (a->axis.userticlabels[a->axis.nuserticlabels], start, len);
#else
      bcopy(start, a->axis.userticlabels[a->axis.nuserticlabels], len);
#endif
      a->axis.userticlabels[a->axis.nuserticlabels][len] = '\0';
      a->axis.nuserticlabels++;
      if (*cur == '\0') break;
      start = cur++;
    }
    else
      cur++;
  }
  a->axis.userticlabels = 
    (String *) XtRealloc(a->axis.userticlabels, 
			 a->axis.nuserticlabels * sizeof(String));
}

/**********************************************************************/


#define FreeLabelGC(a)     XtReleaseGC(a, a->axis.labelGC);
#define FreeNumberGC(a)    XtReleaseGC(a, a->axis.numberGC);
#define FreeAxisGC(a)      XtReleaseGC(a, a->axis.axisGC);
#define FreeDottedGC(a)    XtReleaseGC(a, a->axis.dottedGC);

#define GetLabelFF(a)  a->axis.labelFF = AtFontFamilyGet(XtDisplay(XtParent((Widget)a)), a->axis.labelFontFamily);

#define GetNumberFF(a)     a->axis.numberFF =AtFontFamilyGet(XtDisplay(XtParent((Widget)a)), a->axis.numberFontFamily);

#define GetNumberFont(a)  a->axis.numberFont = AtFontFetch(a->axis.numberFF, a->axis.numberStyle, a->axis.numberSize);

#define FreeLabelFF(a)  AtFontFamilyRelease(a->axis.labelFF);
#define FreeNumberFF(a)  AtFontFamilyRelease(a->axis.numberFF);

/**********************************************************************/

static void AxisClassInitialize(void)

{
  AtRegisterFontStyleConverter();
}

/**********************************************************************/

static void AxisInitialize(Widget request, Widget new)
{
  AtAxisObject a = (AtAxisObject) new;

  a->axis.scale = AtScaleCreate(a->axis.axis_min,a->axis.axis_max,
				0,0, a->axis.transform);
  CheckScaleValue(a);

  GetLabelFF(a);
  GetNumberFF(a);
  GetNumberFont(a);

  CopyLabel(a);
  GetLabelText(a);

  GetAxisGC(a);
  GetDottedGC(a);
  GetNumberGC(a);
  GetLabelGC(a);
    
  /* Parse the ticLabels and make a private copy.   Should work even if
     no ticLabels are present. */

  a->axis.ticlabelcpy = AtNewString(a->axis.ticLabels);
  ParseTicLabels(a);

  a->axis.lines = a->axis.dlines = NULL;
  a->axis.ticvalues = (double*) XtMalloc(0);
  a->axis.ticcoords = (short *) XtMalloc(0);
  a->axis.ticlabels = (char **) XtMalloc(0);
  a->axis.subticcoords = (short *)XtMalloc(0);
  a->axis.ntics = 0;
  if (!a->axis.autoNumber) RecalcNumTics(a);

  a->axis.valid = False;

  a->axis.labelX = a->axis.labelY = 0;

  if (a->axis.ticDensity > 5) a->axis.ticDensity = 5;
  if (a->axis.ticDensity < -5) a->axis.ticDensity = -5;
}

/**********************************************************************/

static void AxisDestroy(Widget w)
{
  AtAxisObject a = (AtAxisObject) w;
  int i;

  AtScaleDestroy(a->axis.scale);
  FreeLabel(a);
  FreeLabelText(a);
  FreeLabelFF(a);
  FreeNumberFF(a);
  FreeAxisGC(a);
  FreeDottedGC(a);
  FreeNumberGC(a);
  FreeLabelGC(a);
  XtFree(a->axis.ticvalues);
  XtFree(a->axis.ticcoords);
  XtFree(a->axis.subticcoords);
  for(i=0;i<a->axis.nticlabels;i++) XtFree(a->axis.ticlabels[i]);
  XtFree(a->axis.ticlabels);
  for (i = 0; i < a->axis.nuserticlabels; i++)
    XtFree(a->axis.userticlabels[i]);
  XtFree(a->axis.userticlabels);
  XtFree(a->axis.ticlabelcpy);
}

/**********************************************************************/

static Boolean AxisSetValues(AtAxisObject current,
			     AtAxisObject request,
			     AtAxisObject new)
{
#define Changed(field) (current->axis.field != new->axis.field)
  Boolean redraw = False;
  Boolean ticsChanged = False, compticspacing = False;
  Boolean labelchanged = False, labelffchanged = False;
  int i, len;

  /* free/reallocate AtText, FontFamilies, GCs, etc */
  if (Changed(label)) {
    FreeLabel(current);
    CopyLabel(new);
    labelchanged = True;
  }
  if (Changed(labelFontFamily)) {
    FreeLabelFF(new);
    GetLabelFF(new);
    labelffchanged = True;
  }
  if (labelchanged || labelffchanged || Changed(labelSize)) {
    FreeLabelText(new);
    GetLabelText(new);
    redraw = True;
  }
    
  if (Changed(axisColor)) {
    FreeAxisGC(new);
    GetAxisGC(new);
    redraw = True;
  }
  if (Changed(labelColor)) {
    FreeLabelGC(new);
    GetLabelGC(new);
    redraw = True;
  }
  if (Changed(numberFontFamily)) {
    FreeNumberFF(new);
    GetNumberFF(new);
    redraw = True;
  }
  if (Changed(numberFontFamily)||Changed(numberSize)||Changed(numberStyle)) {
    GetNumberFont(new);
    redraw = True;
  }
  if (Changed(numberColor) || Changed(numberFontFamily) ||
      Changed(numberSize) || Changed(numberStyle)) {
    FreeNumberGC(new);
    GetNumberGC(new);
    redraw = True;
  }
    
  if (Changed(ticDensity)) {
    if (new->axis.ticDensity > 5) new->axis.ticDensity = 5;
    if (new->axis.ticDensity < -5) new->axis.ticDensity = -5;
    redraw = True;
  }

  /* deal with changes to the axis layout itself. */
  if (Changed(axis_min) || Changed(axis_max))  {
    AtScaleRescale(new->axis.scale, new->axis.axis_min, new->axis.axis_max);
    CheckScaleValue(new);
  }
  if (Changed(transform))  {
    AtScaleChangeTransform(new->axis.scale, new->axis.transform);
    CheckScaleValue(new);
  }
    
  if (Changed(axis_min) || Changed(axis_max) || Changed(transform) ||
      Changed(numbersOutside) || Changed(ticDensity) ||
      Changed(subticDensity) || Changed(autoNumber) || 
      (!new->axis.autoNumber && (Changed(ticInterval)||Changed(subtics)))) {
    if (!new->axis.autoNumber) RecalcNumTics(new);
    compticspacing = True;
    ticsChanged = True;
    redraw = True;
  }

  if (ticsChanged || Changed(label) || Changed(labelFontFamily) ||
      Changed(numberFontFamily) || Changed(labelSize) ||
      Changed(numberSize) || Changed(numberStyle)) {
    redraw = True;
  }

  if (Changed(drawGrid)) redraw = True;

  if (Changed(ticLabels) || (new->axis.ticLabels &&
			     new->axis.ticlabelcpy &&
			     strcmp(new->axis.ticLabels,
				    new->axis.ticlabelcpy)))  {
    for (i = 0; i < new->axis.nuserticlabels; i++)
      XtFree(new->axis.userticlabels[i]);
    XtFree(new->axis.userticlabels);
    XtFree(new->axis.ticlabelcpy);
    new->axis.ticlabelcpy = AtNewString(new->axis.ticLabels);
    ParseTicLabels(new);
    compticspacing = True;
  }

  if (compticspacing)  {
    if (new->axis.vertical)
      len = new->axis.y2 - new->axis.y1;
    else
      len = new->axis.x2 - new->axis.x1;
    AtAxisComputeTicSpacing(new, len);
  }

  if (redraw) 
    XtCallCallbacks(new, XtNaxisNeedsRedraw, NULL);
  
  return False;
#undef Changed    
}

/**********************************************************************/
/**********************************************************************/

/************************************************************/
/****         axis.c                                  *******/
/************************************************************/
/*  Some of the code used in this file is taken from       **/
/*  gnuplot, especially the code for calculating number    **/
/*  and locations of log ticks                             **/
/************************************************************/

#define ALMOSTZERO 1.0e-12
#define PI 3.141592653589
#define fequal(x,y) (fabs((x)-(y)) < ALMOSTZERO)
#define fzero(x) (fabs(x) < ALMOSTZERO)


/****************** ffix *********************
 used to round a number up or down to the nearest
 integer if that number of extremely close to that
 integer. This prevents seeing numbers like:
 1.99999999999 or 1.000000001
 *********************************************/
static double ffix(double x)
{
  if (fequal(x,ceil(x))) return ceil(x);
  else if (fequal(x,floor(x))) return floor(x);
  else return x;
}

/**********************************************************************/

void AtAxisDraw(Display *dpy, Drawable win, AtAxisObject axis, Pixel bg)
{
  AtAxisPart *a = &axis->axis;
  int i;
  short st1, st2, bt1, bt2; /* where to draw small and big tics */
  short tmp;

  /* draw the line */
  XDrawLine(dpy, win, a->axisGC, a->x1, a->y1, a->x2, a->y2);

#define swap(a,b) {tmp = a; a = b; b = tmp;}    
  /* figure out how to draw the tics */
  st1 = st2 = 0;
  bt1 = bt2 = 0;
  if (a->vertical) {
    if (a->ticsOutside) {st1 = a->subticLength; bt1 = a->ticLength;}
    if (a->ticsInside)  {st2 = a->subticLength; bt2 = a->ticLength;}
    if (a->mirror) { swap(st1, st2); swap(bt1,bt2); }
  } else {
    if (a->ticsOutside) {st2 = a->subticLength; bt2 = a->ticLength;}
    if (a->ticsInside)  {st1 = a->subticLength; bt1 = a->ticLength;}
    if (a->mirror) { swap(st1, st2); swap(bt1,bt2); }
  }
#undef swap
    
  /* draw the tics */
  if (a->vertical) 
    for(i = 0; i < a->ntics; i++) 
      XDrawLine(dpy, win, a->axisGC,
		a->x1 - bt1, a->ticcoords[i],
		a->x1 + bt2, a->ticcoords[i]);
  else
    for(i=0; i<a->ntics; i++)
      XDrawLine(dpy, win, a->axisGC,
		a->ticcoords[i], a->y1 - bt1,
		a->ticcoords[i], a->y1 + bt2);

  /* draw the subtics */
  if (a->vertical) 
    for(i = 0; i < a->totalsubtics; i++)
      XDrawLine(dpy, win, a->axisGC,
		a->x1 - st1, a->subticcoords[i],
		a->x1 + st2, a->subticcoords[i]);
  else
    for(i = 0; i < a->totalsubtics; i++)
      XDrawLine(dpy, win, a->axisGC,
		a->subticcoords[i], a->y1 - st1,
		a->subticcoords[i], a->y1 + st2);
  
  /* draw the numbers */
  a->maxnumwidth = 0;
  if (a->drawNumbers) {
    for(i=0; i < a->ntics; i++) {
      int x,y,wid,len;
      len = strlen(a->ticlabels[i]);
      wid = XTextWidth(a->numberFont, a->ticlabels[i], len);
      if (wid > a->maxnumwidth) a->maxnumwidth = wid;
      if (a->vertical) {
	x = a->x1;
	y = a->ticcoords[i];
	if (!a->mirror ^ !!a->numbersOutside) 
	  x += bt2 + 2;
	else
	  x -= bt1 + 2 + wid;
	y += a->numberFont->ascent/2 - a->numberFont->descent/2;
      }
      else {
	x = a->ticcoords[i];
	y = a->y1;
	if (!a->mirror ^ !!a->numbersOutside)
	  y -= bt1 + 2 + a->numberFont->descent;
	else
	  y += bt2 + 2 + a->numberFont->ascent;
	x -= wid/2;
      }
      XDrawString(dpy, win, a->numberGC, x, y, a->ticlabels[i], len);
    }
  }

  /* draw the label */
  if (a->labelText) {
    int x,y;
    x = (a->x1 + a->x2)/2;
    y = (a->y1 + a->y2)/2;
    if (a->vertical) {
      if (a->mirror) 
	x += bt2 + 2 + a->maxnumwidth;
      else
	x -= bt1 + a->maxnumwidth + 2 + AtTextWidth(a->labelText);
      y += AtTextAscent(a->labelText)/2 - AtTextDescent(a->labelText)/2;
    }
    else {
      if (a->mirror) {
	y -= bt1 + 2 +  AtTextDescent(a->labelText);
	if (a->drawNumbers)
	  y -= a->numberFont->ascent + a->numberFont->descent;
      }
      else {
	y += bt2 + 2 + AtTextAscent(a->labelText);
	if (a->drawNumbers)
	  y +=  a->numberFont->ascent + a->numberFont->descent;
      }
      x -= AtTextWidth(a->labelText)/2;
    }
    XSetBackground(dpy, a->labelGC, bg);
    AtTextDraw(dpy, win, a->labelGC, a->labelText,x,y);
  }
}

/**********************************************************************/

void AtAxisDrawGrid(Display *dpy, Window win, AtAxisObject w, 
		    int from, int to)
{
  int i;

  if (w->axis.vertical) {
    for(i = 0; i < w->axis.ntics; i++)
      XDrawLine(dpy, win, w->axis.dottedGC,
		from, w->axis.ticcoords[i],
		to, w->axis.ticcoords[i]);
  }
  else {
    for(i = 0; i < w->axis.ntics; i++)
      XDrawLine(dpy, win, w->axis.dottedGC,
		w->axis.ticcoords[i], from,
		w->axis.ticcoords[i], to);
  }
}

/**********************************************************************/

void AtAxisDrawGridRegion(Display *dpy, Window win, AtAxisObject w, 
			  int from, int to, Region region)
{
  int i, clipx, clipy;
  Pixmap clip_mask;

  clipx = w->axis.dottedGC->values.clip_x_origin;
  clipy = w->axis.dottedGC->values.clip_y_origin;
  clip_mask = w->axis.dottedGC->values.clip_mask;
  XSetRegion(dpy, w->axis.dottedGC, region);

  if (w->axis.vertical) {
    for(i = 0; i < w->axis.ntics; i++)
      XDrawLine(dpy, win, w->axis.dottedGC,
		from, w->axis.ticcoords[i],
		to, w->axis.ticcoords[i]);
  }
  else {
    for(i = 0; i < w->axis.ntics; i++)
      XDrawLine(dpy, win, w->axis.dottedGC,
		w->axis.ticcoords[i], from,
		w->axis.ticcoords[i], to);
  }
 
  XSetClipOrigin(dpy, w->axis.dottedGC, clipx, clipy);
  XSetClipMask(dpy, w->axis.dottedGC, clip_mask);
}

/**********************************************************************/

static void RecalcNumTics(AtAxisObject w)
{
  short oldntics;
  int i;
  AtAxisPart *axis = &w->axis;

  oldntics = w->axis.ntics;

  if (axis->transform == AtTransformLINEAR)  {
    double axis_min = axis->axis_min;
    double axis_max = axis->axis_max;
    axis_min = ceil(axis_min/axis->ticInterval) * axis->ticInterval;
    axis_max = floor(axis_max/axis->ticInterval) * axis->ticInterval;
    w->axis.ntics = 1+(int)(fabs(axis_max - axis_min)/w->axis.ticInterval);
  }
  else
    axis->ntics = 1+(int)((log10(axis->axis_max) - log10(axis->axis_min))
			  * 10.0/w->axis.ticInterval);

  if (w->axis.ntics < 0 || w->axis.ntics > 10000) {
    /* Obviously a major error - we hope an ensuing call to
       AtAxisChangeNumbering will alter ticInterval! */
    w->axis.ntics = oldntics;
  }
  
  if (w->axis.ntics != oldntics) {
    axis->ticvalues = (double *)XtRealloc(axis->ticvalues,
					  (axis->ntics) * sizeof(double));
    axis->ticcoords = (short *)XtRealloc(axis->ticcoords,
					 (axis->ntics) * sizeof(short));
    for(i=0; i<oldntics; i++)
      if (axis->ticlabels[i]!=NULL) XtFree(axis->ticlabels[i]);
    axis->ticlabels = (char **)XtRealloc(axis->ticlabels,
					 (axis->ntics) * sizeof(char *));
    axis->nticlabels = axis->ntics;
    for(i=0; i<(axis->ntics); i++) axis->ticlabels[i] = NULL;
  }
  axis->subticcoords = (short *)XtRealloc(axis->subticcoords, sizeof(short) *
					    (axis->ntics)*(axis->subtics));
}

/**********************************************************************/

/* calculate ticInterval & subtics based on ticDensity and subticDensity */
static void AutoNumber(AtAxisObject w, int length)
{
  AtAxisPart *axis = &w->axis;
  int numtics;
  int fontheight;
  int numsubtics;

  /*
   * the suggested number of tics depends on the size of the axis,
   * the size of the font, and the value of ticDensity
   */
  fontheight = axis->numberFont->ascent + axis->numberFont->descent;
  numtics = (length/(fontheight * (3 - axis->ticDensity/2.0)));
  if (numtics == 0) numsubtics = 0;
  else
    numsubtics = (int) (length/numtics) / (8 - axis->subticDensity);
    
  if (axis->transform == AtTransformLINEAR){
    double mag, flr, d, sizeticratio;
    int mult;
	
    if (numtics < 1) numtics = 1;
    mag = log10(fabs(axis->axis_max - axis->axis_min));
    flr = floor(mag);
    sizeticratio = pow(10.0, mag-flr)/ numtics;
	
    d = 1.0;
	
    /* The ratio thresholds were calculated to split
       the difference in the resulting number of ticks */
    while(1){
      if (sizeticratio > 2.857*d){
	mult = 5;
	break;
      }
      if (sizeticratio > 1.333*d){
	mult = 2;
	break;
      }
      if (sizeticratio > 0.6666*d){
	mult = 1;
	break;
      }
      d /= 10.;
    }
    axis->ticInterval =  mult * d * pow(10.0,flr);

    /*
     * now figure out subtics.
     * if it makes sense to do 5 or 10 subdivision, do it.
     * otherwise do a power of 2
     */
    switch (mult) {
    case 1:
      if (numsubtics >= 10) axis->subtics = 9;
      else if (numsubtics >= 5) axis->subtics = 4;
      else if (numsubtics >= 2) axis->subtics = 1;
      else axis->subtics = 0;
      break;
    case 2:
      if (numsubtics >= 8) axis->subtics = 7;
      else if (numsubtics >= 4) axis->subtics = 3;
      else if (numsubtics >= 2) axis->subtics = 1;
      else axis->subtics = 0;
      break;
    case 5:
      if (numsubtics >= 10) axis->subtics = 9;
      else if (numsubtics >= 5) axis->subtics = 4;
      else if (numsubtics >= 2) axis->subtics = 1;
      else axis->subtics = 0;
      break;
    }
  }
  else {
    double tpd = numtics/log10(axis->axis_max)- log10(axis->axis_min);
	
    if (tpd > 7.5) {
      axis->ticInterval = 1.0;
      if (numsubtics >= 4) axis->subtics = 3;
      else if (numsubtics >= 1) axis->subtics = 1;
      else axis->subtics = 0;
    }
    else if (tpd > 3.5) {
      axis->ticInterval = 2.0;
      if (numsubtics >= 4) axis->subtics = 3;
      else if (numsubtics >= 1) axis->subtics = 1;
      else axis->subtics = 0;
    }
    else if (tpd > 1.5) {
      axis->ticInterval =  5.0;
      if (numsubtics >= 10) axis->subtics = 9;
      else if (numsubtics > 5) axis->subtics = 4;
      else if (numsubtics > 1) axis->subtics = 1;
      else axis->subtics = 0;
    }
    else {
      axis->ticInterval =  10.0;
      if (numsubtics >= 10) axis->subtics = 9;
      else if (numsubtics >= 5) axis->subtics = 4;
      else if (numsubtics >= 1) axis->subtics = 1;
      else axis->subtics = 0;
    }
  }
  
  RecalcNumTics(w);
}

/**********************************************************************/

/*
 * transform the real tic coordinates into pixel coordinates
 * Also compute where subtics should go.
 */
void AtAxisComputeTics(AtAxisObject w)
{
  int i,j;
  double subticval;
  
  w->axis.totalsubtics = 0;
  for(i = 0; i < w->axis.ntics; i++) {
    w->axis.ticcoords[i] = AtScaleUserToPixel(w->axis.scale,
					      w->axis.ticvalues[i]);
    if (w->axis.scale->transform == AtTransformLINEAR)
      for(j=0; j < w->axis.subtics; j++) {
	if ((subticval = w->axis.ticvalues[i] + 
	     w->axis.ticInterval / (w->axis.subtics+1)*(j+1)) <= 
	     max(w->axis.axis_max, w->axis.axis_min))  {
	  w->axis.subticcoords[i*(w->axis.subtics)+j] = 
	    AtScaleUserToPixel(w->axis.scale, subticval);
	  w->axis.totalsubtics++;
	}
      }
    else {
      int decade;
      double ticval, logticval;
      ticval = w->axis.ticvalues[i];
      logticval = log10(ticval);
      decade = (int)floor(logticval);
      if (logticval == (double)decade) /* if first tic of a decade */
	ticval = 0.0;
      
      for(j=0; j < w->axis.subtics; j++) {
	if ((subticval = AtScaleUserToPixel(w->axis.scale,
					    ticval + (w->axis.ticInterval/
						      (w->axis.subtics+1)*(j+1)) *
					    pow(10.0, (double)decade))) <=
	    w->axis.axis_max)  {
	  w->axis.subticcoords[i*(w->axis.subtics)+j] = ticval;
	  w->axis.totalsubtics++;
	}
      }
    }
  }
}  

/**********************************************************************/

/*
 * This procedure sets up w->axis.ntics, ticvalues & ticlabels
 * It depends on knowing (approximately) how long in pixels the axis is,
 * and what font is to be used.  It sets a->axis.maxticlen for use by
 * AtAxisWidth
 */
void AtAxisComputeTicSpacing(AtAxisObject w, int length)
{
  int i, width;
  AtAxisPart *axis = &w->axis;
  char buf[30];
  int d, firstd, lastd;
  int len;
  double tic;
  int n;

  if (length <= 0) length = 1;
  
  if (axis->autoNumber) AutoNumber(w, length);
    
  /* Calculate the tic values and place them in the ticvalues
     array of the axis structure */
  if (axis->transform == AtTransformLINEAR) {
    double fmin;
    fmin = min(axis->axis_min, axis->axis_max);
    fmin = ceil(fmin/axis->ticInterval) * axis->ticInterval;
    for(i=0;i<axis->ntics;i++)
      axis->ticvalues[i] = ffix(fmin + i*axis->ticInterval);
  }
    
  else {
    double lmin = log10(axis->axis_min);
    double lmax = log10(axis->axis_max);
    firstd = (int) floor(lmin);
    lastd = (int) ceil(lmax);
    n = 0;
    for(d=firstd; d <= lastd; d++) {  /* for each decade */
      if ((d >= lmin) && (d <= lmax))
	axis->ticvalues[n++] = pow(10.0, (double)d);
      for(tic = axis->ticInterval; tic < 10.0; tic+=axis->ticInterval) {
	double ctic = (double) d + log10(tic);
	if ((ctic >= lmin) && (ctic <= lmax))
	  axis->ticvalues[n++] = pow(10.0, ctic);
      }
    }
    axis->ntics = n;
  }
    
  axis->maxnumwidth = 0;
  for (i=0; i < axis->ntics; i++) {
    if (axis->nuserticlabels)
      strcpy(buf, (i < axis->nuserticlabels ? axis->userticlabels[i] : ""));
    else
      sprintf(buf, axis->ticFormat, axis->ticvalues[i] * axis->ticMultiplier);
    len = strlen(buf);
    if (axis->ticlabels[i] != NULL) XtFree(axis->ticlabels[i]);
    axis->ticlabels[i] = strcpy(XtMalloc(len+1), buf);
    width = XTextWidth(axis->numberFont,buf,len);
    if (width > axis->maxnumwidth) axis->maxnumwidth = width;
  }
  AtAxisComputeTics(w);
}

/**********************************************************************/
/*
 * returns the "width" (the height if horizontal) of the passed axis.
 * Only considers that part of the label marked "outside".
 */
int AtAxisWidth(AtAxisObject w)
{
  if (w->axis.vertical) {
    return ((w->axis.labelText) ? AtTextWidth(w->axis.labelText) : 0) +
      ((w->axis.drawNumbers && w->axis.numbersOutside) ?
       w->axis.maxnumwidth : 0) +
	 ((w->axis.ticsOutside) ? w->axis.ticLength + 1 : 0) +
	   MIN_MARGIN;
  }
  else {
    return ((w->axis.labelText) ? AtTextHeight(w->axis.labelText) : 0) + 
      ((w->axis.drawNumbers && w->axis.numbersOutside) ?
       w->axis.numberFont->ascent+ w->axis.numberFont->descent : 0) +
	 ((w->axis.ticsOutside) ? w->axis.ticLength + 1 : 0) +
	   MIN_MARGIN;
  }
}

/**********************************************************************/
		   
void AtAxisChangeBounds(AtAxisObject w, double axis_min, double axis_max)
{
  w->axis.axis_min = axis_min;
  w->axis.axis_max = axis_max;
  AtScaleRescale(w->axis.scale, axis_min, axis_max);
  CheckScaleValue(w);
  if (!w->axis.autoNumber) RecalcNumTics(w);
  AtAxisComputeTicSpacing(w, AtScaleGetHighPix(w->axis.scale) -
			  AtScaleGetLowPix(w->axis.scale));
}

/**********************************************************************/

void AtAxisChangeNumbering(AtAxisObject w, double interval, int subtics)
{
  w->axis.ticInterval = interval;
  w->axis.subtics = subtics;
  RecalcNumTics(w);
  AtAxisComputeTicSpacing(w, AtScaleGetHighPix(w->axis.scale) -
			  AtScaleGetLowPix(w->axis.scale));
}

/**********************************************************************/

void AtAxisGetBounds(AtAxisObject w, double *axis_min, double *axis_max)
{
  *axis_min = w->axis.axis_min;
  *axis_max = w->axis.axis_max;
}

/**********************************************************************/

void AtAxisSetLocation(AtAxisObject w, int x1, int y1, int x2, int y2)
{
  Boolean vertical;
    
  w->axis.x1 = x1;
  w->axis.y1 = y1;
  w->axis.x2 = x2;
  w->axis.y2 = y2;

  if (x1 == x2) vertical = True;
  else vertical = False;
  
  if (vertical) AtScaleResize(w->axis.scale, y2, y1);
  else AtScaleResize(w->axis.scale, x1, x2);
  
  AtAxisComputeTics(w);
}

/**********************************************************************/

void AtAxisGetLocation(AtAxisObject w, int *x1, int *y1, int *x2, int *y2)
{
  *x1 = w->axis.x1;
  *y1 = w->axis.y1;
  *x2 = w->axis.x2;
  *y2 = w->axis.y2;
}

/**********************************************************************/

static void CheckScaleValue(AtAxisObject a)
{
  char *emessage;

  switch (AtScaleGetErrorCode(a->axis.scale))  {
  case SCALEERROR_NONE:
    return;
  case SCALEERROR_LOGNEGATIVE:
    emessage = AtScaleGetErrorMessage(a->axis.scale);
    a->axis.axis_min = _AtScaleAlmostZero;
    AtScaleRescale(a->axis.scale, a->axis.axis_min, a->axis.axis_max);
    AtScaleChangeTransform(a->axis.scale, a->axis.transform);
    CheckScaleValue(a);    
    break;
  case SCALEERROR_BOUNDLESS:
    emessage = AtScaleGetErrorMessage(a->axis.scale);
    a->axis.axis_max = a->axis.axis_min + 1.0;
    AtScaleRescale(a->axis.scale, a->axis.axis_min, a->axis.axis_max);
    AtScaleChangeTransform(a->axis.scale, a->axis.transform);
    CheckScaleValue(a);    
    break;
  case SCALEERROR_BOUNDCLOSE:
    emessage = AtScaleGetErrorMessage(a->axis.scale);
    a->axis.axis_max = _AtScaleAlmostZero;
    AtScaleRescale(a->axis.scale, a->axis.axis_min, a->axis.axis_max);
    AtScaleChangeTransform(a->axis.scale, a->axis.transform);
    CheckScaleValue(a);    
    break;
  }
  AxisError(a, emessage);
}

/**********************************************************************/

static void AxisError(AtAxisObject a, char *errortext)
{
  if (a->axis.errorCallback != NULL)
    XtCallCallbacks(a, XtNerrorCallback, errortext);
  else
    XtWarning(errortext);
}
