/* $Id: Axis.c,v 1.1 91/05/20 15:11:23 gnb Exp $ */
/* $Source: /export/sources/x/At/Plotter/tmp/RCS/Axis.c,v $ */

/*

Copyright 1990,1991 by the Massachusetts Institute of Technology

All rights reserved.

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 the Massachusetts
Institute of Technology (M.I.T.) not be used in advertising or publicity
pertaining to distribution of the software without specific, written
prior permission.

M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.

*/

/*
 * $Log:	Axis.c,v $
 * Revision 1.1  91/05/20  15:11:23  gnb
 * Initial revision
 * 
 * 
 */

#include <math.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#ifdef __STDC__
#include <stdlib.h>
#endif

#include <X11/IntrinsicP.h>	/* For XtCheckSubclass */
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>

#ifdef _AtDevelopment_
#include "Scale.h"
#include "AtConverters.h"
#include "FontFamily.h"
#include "Text.h" 
#include "AxisP.h"
#include "Plotter.h"
#else
#include <At/Scale.h>
#include <At/AtConverters.h>
#include <At/FontFamily.h>
#include <At/Text.h>
#include <At/AxisP.h>
#include <At/Plotter.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.label_font_family),
     XtRString, "new century schoolbook"},
  {XtNnumberFontFamily, XtCFontFamily, XtRString,
     sizeof(String), offset(axis.number_font_family),
     XtRString, "new century schoolbook"},
  {XtNlabelSize, XtCFontSize, XtRFontSize,
     sizeof(int), offset(axis.label_size),
     XtRImmediate, (caddr_t) AtFontNORMAL},
  {XtNnumberSize, XtCFontSize, XtRFontSize,
     sizeof(int), offset(axis.number_size),
     XtRImmediate, (caddr_t) AtFontMEDIUM},
  {XtNaxisColor, XtCForeground, XtRPixel,
     sizeof(Pixel), offset(axis.axis_color),
     XtRString, (caddr_t) XtDefaultForeground},
  {XtNnumberColor, XtCForeground, XtRPixel,
     sizeof(Pixel), offset(axis.number_color),
     XtRString, (caddr_t) XtDefaultForeground},
  {XtNlabelColor, XtCForeground, XtRPixel,
     sizeof(Pixel), offset(axis.label_color),
     XtRString, (caddr_t) XtDefaultForeground},
  {XtNmin, XtCMin, XtRDouble,
     sizeof(double), offset(axis.min), XtRDouble, (caddr_t)&zero},
  {XtNmax, XtCMax, XtRDouble,
     sizeof(double), offset(axis.max), XtRDouble, (caddr_t)&one},
  {XtNroundEndpoints, XtCRoundEndpoints, XtRBoolean,
     sizeof(Boolean), offset(axis.round_endpoints), 
     XtRImmediate, (caddr_t)False},
  {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.draw_numbers), XtRImmediate, (caddr_t)True},
  {XtNdrawGrid, XtCDrawGrid, XtRBoolean,
     sizeof(Boolean), offset(axis.draw_grid), XtRImmediate, (caddr_t)False},
  {XtNautoNumber, XtCAutoNumber, XtRBoolean,
     sizeof(Boolean), offset(axis.auto_number), XtRImmediate, (caddr_t)True},
  {XtNticInterval, XtCTicInterval, XtRDouble,
     sizeof(double), offset(axis.tic_interval), XtRDouble,(caddr_t)&one},
  {XtNsubtics, XtCSubtics, XtRShort,
     sizeof(short), offset(axis.subtics), XtRImmediate, (caddr_t)0},
  {XtNticDensity, XtCTicDensity, XtRShort,
     sizeof(short), offset(axis.tic_density), XtRImmediate, (caddr_t)0},
  {XtNsubticDensity, XtCSubticDensity, XtRShort,
     sizeof(short), offset(axis.subtic_density), XtRImmediate,(caddr_t)0},
  {XtNticMultiplier, XtCTicMultiplier, XtRDouble,
     sizeof(double), offset(axis.tic_multiplier), XtRDouble, (caddr_t)&one},
  {XtNticFormat, XtCTicFormat, XtRString,
     sizeof(String), offset(axis.tic_format), XtRString, (caddr_t)"%.3lg"},
  {XtNticsOutside, XtCTicsOutside, XtRBoolean,
     sizeof(Boolean), offset(axis.tics_outside), XtRImmediate, (caddr_t)True},
  {XtNticsInside, XtCTicsInside, XtRBoolean,
     sizeof(Boolean), offset(axis.tics_inside), XtRImmediate, (caddr_t)False},
  {XtNnumbersOutside, XtCNumbersOutside, XtRBoolean,
     sizeof(Boolean), offset(axis.numbers_outside), 
     XtRImmediate, (caddr_t)True},
  {XtNticLength, XtCTicLength, XtRShort,
     sizeof(short), offset(axis.tic_length), XtRImmediate, (caddr_t)5},
  {XtNsubticLength, XtCTicLength, XtRShort,
     sizeof(short), offset(axis.subtic_length), XtRImmediate, (caddr_t)2},
  {XtNerrorCallback, XtCErrorCallback, XtRCallback,
     sizeof(XtCallbackList), offset(axis.errorCallback), XtRCallback, NULL},
  {XtNticLabels, XtCTicLabels, XtRString,
     sizeof(String), offset(axis.tic_labels), XtRImmediate, (caddr_t) NULL},
};

#undef offset

static Boolean AxisSetValues P((AtAxisObject, AtAxisObject, AtAxisObject));
static void AxisClassInitialize P((void));
static void AxisInitialize P((Widget, Widget));
static void AxisDestroy P((Widget));
static void CheckScaleValue P((AtAxisObject));
static void AxisError P((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 P((AtAxisObject a));
static void GetLabelText(a)
AtAxisObject a;
{
  if ((a->axis.label != NULL) && (*a->axis.label != '\0')) {
    a->axis.label_text = 
      AtTextCreate(a->axis.label, a->axis.labelFF, a->axis.label_size);
    if (a->axis.vertical == True) AtTextRotate(a->axis.label_text);
  }
  else
    a->axis.label_text = NULL;
}

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

static void FreeLabelText P((AtAxisObject a));
static void FreeLabelText(a)
AtAxisObject a;
{
  if (a->axis.label_text)
    AtTextDestroy(a->axis.label_text);
  a->axis.label_text = NULL;
  
}

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

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

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

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

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

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

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

static void GetNumberGC P((AtAxisObject a)); 
static void GetNumberGC(a)
AtAxisObject a;
{
  XGCValues gcv;

  gcv.foreground = a->axis.number_color; 
  a->axis.numberGC = XtGetGC(XtParent((Widget)a), GCForeground, &gcv);
}

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

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

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

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

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

static void FreeTicValueStuff P((AtAxisObject a)); 
static void FreeTicValueStuff(a)
AtAxisObject a;
{
  int i;
  
  for (i = 0; i < a->axis.tics; i++) {
    XtFree(a->axis.tic_label_array[i]);
    AtTextDestroy(a->axis.tic_text[i]);
  }
  XtFree((char *)a->axis.tic_label_array);
  XtFree((char *)a->axis.tic_text);
  XtFree((char *)a->axis.tic_values);
  a->axis.tic_values = NULL;
  a->axis.tic_label_array = NULL;
  a->axis.tic_text = NULL;
  a->axis.tics = 0;
}
  
static void FreeTicPixelStuff P((AtAxisObject a)); 
static void FreeTicPixelStuff(a)
AtAxisObject a;
{
  XtFree((char *)a->axis.tic_coords);
  a->axis.tic_coords = NULL; 
}

/**********************************************************************
 *
 * Parse the user-supplied tic labels into user_tic_labels and set
 * num_user_tic_labels as appropriate.  Allocate and freethe copy of
 * tic_labels also.
 */
static void FreeUserTicLabels P((AtAxisObject a)); 
static void FreeUserTicLabels(a)
AtAxisObject a;
{
  int i;

  for (i = 0; i < a->axis.num_user_tic_labels; i++) {
    XtFree((char *)a->axis.user_tic_labels[i]);
  }
  XtFree((char *)a->axis.user_tic_labels);
  XtFree((char *)a->axis.tic_labels);
  
  a->axis.user_tic_labels = NULL;
  a->axis.num_user_tic_labels = 0;
  a->axis.tic_labels = NULL;
}

static void ParseUserTicLabels P((AtAxisObject a)); 
static void ParseUserTicLabels(a)
AtAxisObject a;
{
  int i, max;
  char *p, *np;

  if (!a->axis.tic_labels) return;
  a->axis.tic_labels = AtNewString(a->axis.tic_labels);
  
  for (i = max = 0, p = a->axis.tic_labels; p && *p; i++, p = np) {
    if (!(np = strchr(p, '\n'))) np = strchr(p, '\0');
    if (i >= max) {
      a->axis.user_tic_labels = 
	(String *)XtRealloc((char *)a->axis.user_tic_labels, 
			    (max += 10) * sizeof (String));
    }
    a->axis.user_tic_labels[i] = strncpy(XtMalloc(np - p + 1), p, np - p);
    a->axis.user_tic_labels[i][np - p] = '\0'; 
    if (*np == '\n') np++;
  }
  a->axis.num_user_tic_labels = i;
}

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

static void AxisClassInitialize P((void)); 
static void AxisClassInitialize()
{
  AtRegisterFontStyleConverter();
}

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

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

  a->axis.scale = AtScaleCreate(a->axis.min,a->axis.max,
				0,0, a->axis.transform);
  CheckScaleValue(a);

  GetLabelFF(a);
  GetNumberFF(a);

  CopyLabel(a);
  GetLabelText(a);

  GetAxisGC(a);
  GetDottedGC(a);
  GetNumberGC(a);
  GetLabelGC(a);
    
  if (a->axis.tic_density > 5) a->axis.tic_density = 5;
  if (a->axis.tic_density < -5) a->axis.tic_density = -5;

  if (a->axis.tic_labels) ParseUserTicLabels(a);
}

/*
 * Free the parsed date label stuff
 */
static void FreeDateLabelStuff P((AtAxisObject a));
static void FreeDateLabelStuff(a)
     AtAxisObject a;
{
  int i;

  for (i = 0; i < a->axis.num_date_labels; i++) {
    XtFree(a->axis.date_label_array[i]);
  }
  XtFree((char *)a->axis.date_label_array);
  XtFree((char *)a->axis.date_label_values);
  a->axis.date_label_values = NULL;
  a->axis.date_label_array = NULL;
  a->axis.num_date_labels = 0;
}


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

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

  AtScaleDestroy(a->axis.scale);
  FreeLabel(a);
  FreeLabelText(a);
  FreeLabelFF(a);
  FreeNumberFF(a);
  FreeAxisGC(a);
  FreeDottedGC(a);
  FreeNumberGC(a);
  FreeLabelGC(a);
  FreeTicValueStuff(a);
  FreeTicPixelStuff(a);
  FreeUserTicLabels(a);
  FreeDateLabelStuff(a); 
}

/**********************************************************************
 * 
 * Routines for doing serious calculation on Axis values.
 *
 * The first four are concerned with user values, the fifth with
 * pixel values.
 * 
 */

/*********************************************************************
 *
 * MakeTicsFromDateLabels()
 *
 * Given the datelabels stuff, make the tic values and labels and stuff
 */
static void MakeTicsFromDateLabels P((AtAxisObject a));
static void MakeTicsFromDateLabels(a)
     AtAxisObject a;
{
  int n, i;

  for (i = n = 0; i < a->axis.num_date_labels; i++, n++) {
    if (a->axis.date_label_values[i] > a->axis.max) break;
  }
  if (a->axis.date_label_values[0] > a->axis.min) n++;
  if (a->axis.date_label_values[i - 1] < a->axis.max) n++;
  
  a->axis.tic_values = (double *)XtMalloc(n * sizeof (double));
  a->axis.tic_label_array = (String *)XtMalloc(n * sizeof (String));
  a->axis.tic_text = (AtText **)XtMalloc(n * sizeof (AtText *));

  n = 0;
  
  if (a->axis.date_label_values[0] > a->axis.min) {
    /* An un-labelled tic at the start of the axis */
    a->axis.tic_values[n] = a->axis.min;
    a->axis.tic_label_array[n] = AtNewString("");
    a->axis.tic_text[n] = 
      AtTextCreate(a->axis.tic_label_array[n], a->axis.numberFF,
		   a->axis.number_size);
    n++;
  }
  for (i = 0; i < a->axis.num_date_labels; i++) {
    if (a->axis.date_label_values[i] > a->axis.max) break;
    if (a->axis.date_label_values[i] < a->axis.min) continue;

    a->axis.tic_values[n] = a->axis.date_label_values[i];
    a->axis.tic_label_array[n] =
      AtNewString(a->axis.date_label_array[i]);
    a->axis.tic_text[n] = 
      AtTextCreate(a->axis.tic_label_array[n], a->axis.numberFF,
		   a->axis.number_size);
    n++;
  }
  if (a->axis.date_label_values[i - 1] < a->axis.max) {
    /* And the max, if it is not already there */
    a->axis.tic_values[n] = a->axis.max;
    a->axis.tic_label_array[n] = AtNewString("");
    a->axis.tic_text[n] = 
      AtTextCreate(a->axis.tic_label_array[n], a->axis.numberFF,
		   a->axis.number_size);
    n++;
  }
  
  a->axis.tics = n;
  /*
   * Subtics is interesting......
   * Need to fake tic_interval and choose an appropriate tics... 
   */
  a->axis.tic_interval = floor((a->axis.tic_values[n - 1] -
				a->axis.tic_values[0]) / n + 0.5);
  if (a->axis.tic_interval > 20)
    a->axis.subtics = a->axis.tic_interval / 5 + .5;
  else a->axis.subtics = a->axis.tic_interval - 1;
}

/*********************************************************************
 *
 * AutoscaleTics()
 * Calculate the number of tics and subtics and tic_interval.
 * Depends on axis length, min, max and font size.
 * If the round_endpoints resource is set, make the min and max a
 * multiple of the tic interval.  set tics to the number of tics
 * INCLUDING the endpoints.
 * 
 *
 * NB - care must be taken for y1 > y2 (on vertical axes!)
 */
static void AutoscaleTics P((AtAxisObject a)); 
static void AutoscaleTics(a)
AtAxisObject a;
{
  AtText *tmp_text = 
    AtTextCreate("0", a->axis.numberFF, a->axis.number_size);
  int nt, nst;
  int len = (a->axis.vertical ? a->axis.y1 - a->axis.y2 :
	     a->axis.x2 - a->axis.x1);
  if (len < 0) len = -len;
  
  nt = len / (AtTextHeight(tmp_text) * (3 - a->axis.tic_density/2.0));

  if (nt <= 0) {
    nt = nst = 0;
  } else {
    nst = (len / nt) / (8 - a->axis.subtic_density);
  } 

  if (a->axis.transform == AtTransformLINEAR) {
    double mag, flr, d, sizeticratio;
    int mult;

    if (nt < 1) nt = 1;
    mag = log10(fabs(a->axis.max - a->axis.min));
    flr = floor(mag);
    sizeticratio = pow(10.0, mag-flr)/ nt;

    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.0;
    }
    a->axis.tic_interval =  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 (nst >= 10) nst = 9;
      else if (nst >= 5) nst = 4;
      else if (nst >= 2) nst = 1;
      else nst = 0;
      break;
    case 2:
      if (nst >= 8) nst = 7;
      else if (nst >= 4) nst = 3;
      else if (nst >= 2) nst = 1;
      else nst = 0;
      break;
    case 5:
      if (nst >= 10) nst = 9;
      else if (nst >= 5) nst = 4;
      else if (nst >= 2) nst = 1;
      else nst = 0;
      break;
    }
    a->axis.subtics = nst;
  } else {			/* Is a log scale */
    double tpd = nt / (log10(a->axis.max) - log10(a->axis.min));

    if (tpd > 7.5) {
      a->axis.tic_interval = 1.0;
      if (nst >= 4) nst = 3;
      else if (nst >= 1) nst = 1;
      else nst = 0;
    }
    else if (tpd > 3.5) {
      a->axis.tic_interval = 2.0;
      if (nst >= 4) nst = 3;
      else if (nst >= 1) nst = 1;
      else nst = 0;
    }
    else if (tpd > 1.5) {
      a->axis.tic_interval =  5.0;
      if (nst >= 10) nst = 9;
      else if (nst > 5) nst = 4;
      else if (nst > 1) nst = 1;
      else nst = 0;
    }
    else {
      a->axis.tic_interval =  10.0;
      if (nst >= 10) nst = 9;
      else if (nst >= 5) nst = 4;
      else if (nst >= 1) nst = 1;
      else nst = 0;
    }
    a->axis.subtics = nst;
  }
#ifdef DEBUG
  fprintf(stderr, "In AutoscaleTics, min %f max %f tic_interval %f nst %d\n",
	  a->axis.min, a->axis.max, a->axis.tic_interval, nst);
#endif
}

/*********************************************************************
 *
 * ScaleTics - Set up tics, subtics, tic_interval according to
 * the auto_number flag.  Return TRUE is a rescale was done on the axis.
 */

static Boolean ScaleTics P((AtAxisObject a)); 
static Boolean ScaleTics(a)
AtAxisObject a;
{
  double l, h;
  Boolean rescale = False;
  
  if (a->axis.auto_number) {
    AutoscaleTics(a);
  }
  /* 
   * tic_interval and subtics are specified (either by resources
   * for auto-number = FALSE or by AutoscaleTics).
   * We need to calulate tics, to be the number of tics
   * including endpoints.  Also round endpoints if that is
   * appropriate.
   */
  if (a->axis.transform == AtTransformLINEAR) {
    if (a->axis.round_endpoints) {
      /* Make axis min and max a multiple of tic_interval */
      double mn, mx;
      
      mn = floor(a->axis.min / a->axis.tic_interval) *
	a->axis.tic_interval;
      mx = ceil(a->axis.max / a->axis.tic_interval) *
	a->axis.tic_interval;
      if (mx > a->axis.max || mn < a->axis.min) {
	a->axis.min = mn;
	a->axis.max = mx; 
	AtScaleRescale(a->axis.scale, a->axis.min, a->axis.max);
	rescale = True;
      }
    }
    /* Calculate the real tics, NOT including endpoints */
    l = ceil(a->axis.min / a->axis.tic_interval) *
      a->axis.tic_interval;
    h = floor(a->axis.max / a->axis.tic_interval) *
      a->axis.tic_interval;
    
    a->axis.tics = 1 + (h - l) / a->axis.tic_interval;
    if (h < a->axis.max) a->axis.tics++;
    if (l > a->axis.min) a->axis.tics++;
  } else {
    /* Log scale */
    if (a->axis.round_endpoints) {
      /* Make axis min and max a multiple of tic_interval */
      double mn, mx;
      
      mn = pow(10.0, floor(log10(a->axis.min) / a->axis.tic_interval) * 
	       a->axis.tic_interval);
      mx = pow(10.0, ceil(log10(a->axis.max) / a->axis.tic_interval) * 
	       a->axis.tic_interval);
      if (mx > a->axis.max || mn < a->axis.min) {
	a->axis.min = mn;
	a->axis.max = mx; 
	AtScaleRescale(a->axis.scale, a->axis.min, a->axis.max);
	rescale = True;
      }
    }
    /* Calculate the real tics, NOT including endpoints */
    h = floor(log10(a->axis.max) / a->axis.tic_interval) *
      a->axis.tic_interval; 
    l = ceil(log10(a->axis.min) / a->axis.tic_interval) *
      a->axis.tic_interval;
    
    a->axis.tics = 1 + 
      (log10(a->axis.max) - log10(a->axis.min)) * 10 / a->axis.tic_interval;
    if ( h < log10(a->axis.max)) a->axis.tics++;
    if (l > log10(a->axis.min)) a->axis.tics++;
  }
  if (a->axis.tics < 2)	/* Can happen with small graphs, max<min */
    a->axis.tics = 2;
  return rescale; 
}

/**********************************************************************
 *
 * CalculateTicValues - Calculate the number and location of tics (in
 * user space).  Returnes TRUE if scale (min/max) is changed.
 */
static Boolean CalculateTicValues P((AtAxisObject a)); 
static Boolean CalculateTicValues(a)
AtAxisObject a;
{
  double val;
  int i, mw = 0;
  Boolean rescale;
  
  FreeTicValueStuff(a);

  if (a->axis.date_label_array) {
    MakeTicsFromDateLabels(a);
    return False;
  } 
  
  rescale = ScaleTics(a);
    
  /* Have tics labels, including min + max */
  a->axis.tic_label_array = 
    (String *)XtCalloc(a->axis.tics, sizeof (String));
  a->axis.tic_text = 
    (AtText **)XtCalloc(a->axis.tics, sizeof (AtText *));
  a->axis.tic_values = 
    (double *)XtCalloc(a->axis.tics, sizeof (double));

#ifdef DEBUG
  fprintf(stderr, "In CalculateTicValues: min %f max %f ntics %d int %f\n", 
	  a->axis.min, a->axis.max, a->axis.tics,
	  a->axis.tic_interval);
#endif 
  if (a->axis.transform == AtTransformLINEAR) {
    for (i = 0, val = a->axis.min; 
	 val < a->axis.max + a->axis.tic_interval * .999;
	 val += a->axis.tic_interval, i++) {
      assert(i < a->axis.tics);
      if (val > a->axis.max) val = a->axis.max; 
      a->axis.tic_values[i] = val;
      if (!i)			
	/* round down to nearest multiple of interval */
	val = floor(val / a->axis.tic_interval) *
	  a->axis.tic_interval;
    } 
  } else {
    /* Log transform! */
    double lmin = log10(a->axis.min);
    double lmax = log10(a->axis.max); 
    int fd = floor(lmin);
    int ld = ceil(lmax);
    int d;
    
    for (i = 0, d = fd; d <= ld; d++) {
      if (d >= lmin && d <= lmax) {
	assert(i < a->axis.tics);
	a->axis.tic_values[i++] = pow(10.0, (double)d);
      }
      for (val = a->axis.tic_interval; val < 10.0; 
	   val += a->axis.tic_interval) {
	double v = d + log10(val);

	if (v >= lmin && i == 0 || v <= lmax && i < a->axis.tics) {
	  assert(i < a->axis.tics);
	  a->axis.tic_values[i++] = pow(10.0, v);
	}
      }
    }
  }

  assert(i == a->axis.tics);
  
  /* Now set up tic labels */
  for (i = 0; i < a->axis.tics; i++) {
    char lbl[30];
    
    if (a->axis.user_tic_labels) {
      a->axis.tic_label_array[i] = 
	AtNewString(i < a->axis.num_user_tic_labels ?
		    a->axis.user_tic_labels[i] : "");
    } else {
      sprintf(lbl, a->axis.tic_format,
	      a->axis.tic_values[i] * a->axis.tic_multiplier);
      a->axis.tic_label_array[i] = AtNewString(lbl);
    }
    a->axis.tic_text[i] = 
      AtTextCreate(a->axis.tic_label_array[i], a->axis.numberFF,
		   a->axis.number_size);
    if (AtTextWidth(a->axis.tic_text[i]) > mw)
      mw = AtTextWidth(a->axis.tic_text[i]);
  }
  a->axis.max_num_width = mw;
  return rescale; 
}

/**********************************************************************
 *
 * CalculateTicPixels
 *
 * Builds the segment arrays for the line, the tics and the grid.
 * Sets up the tic_coord array.
 *
 * NB: x1, y1 is TOP LH corner, x2,y2 is BOTTOM RH corner.
 */
static void CalculateTicPixels P((AtAxisObject a)); 
static void CalculateTicPixels(a)
AtAxisObject a;
{
  int i, tll, tlr;
  XSegment *xp;
  int ntic;
  double v; 
  
  FreeTicPixelStuff(a);
  a->axis.tic_coords = 
    (short *)XtCalloc(a->axis.tics, sizeof (short));
  ntic = a->axis.tics * (a->axis.subtics + 1) + 1;
  a->axis.tic_segments = (XSegment *)XtCalloc(ntic, sizeof (XSegment));
  a->axis.grid_segments =
    (XSegment *)XtCalloc(a->axis.tics, sizeof (XSegment));

  xp = a->axis.tic_segments;
  
  /* First, the actual axis line */
  xp->x1 = a->axis.x1;
  xp->y1 = a->axis.y1;
  xp->x2 = a->axis.x2;
  xp->y2 = a->axis.y2;
  xp++;

  /* Now the main tics */
  if (a->axis.tics_inside || a->axis.tics_outside) {
    tll = tlr = 0; 
    if (a->axis.tics_outside && !a->axis.mirror || 
	a->axis.tics_inside && a->axis.mirror)
      tll = a->axis.tic_length;
    if (a->axis.tics_outside && a->axis.mirror || 
	a->axis.tics_inside && !a->axis.mirror)
      tlr = a->axis.tic_length;
    
    if (a->axis.vertical) {
      for (i = 0; i < a->axis.tics; i++, xp++) {
	assert(xp - a->axis.tic_segments < ntic);
	xp->x1 = a->axis.x1 - tll;
	xp->x2 = a->axis.x2 + tlr;
	xp->y1 = xp->y2 = a->axis.tic_coords[i] =
	  AtScaleUserToPixel(a->axis.scale, a->axis.tic_values[i]);
      }
    } else {
      /* Horizontal */
      for (i = 0; i < a->axis.tics; i++, xp++) {
	assert(xp - a->axis.tic_segments < ntic);
	xp->y1 = a->axis.y1 + tll;
	xp->y2 = a->axis.y2 - tlr;
	xp->x1 = xp->x2 = a->axis.tic_coords[i] =
	  AtScaleUserToPixel(a->axis.scale, a->axis.tic_values[i]);
      }
    }
  }
  
  /* Now the subtics, if any */
  if (a->axis.subtic_length > 0 && a->axis.subtics > 0 && 
      (a->axis.tics_inside || a->axis.tics_outside)) {
    int j;
    double stv = a->axis.tic_interval / (a->axis.subtics + 1);
    
    tll = tlr = 0; 
    if (a->axis.tics_outside && !a->axis.mirror || 
	a->axis.tics_inside && a->axis.mirror)
      tll = a->axis.subtic_length;
    if (a->axis.tics_outside && a->axis.mirror || 
	a->axis.tics_inside && !a->axis.mirror)
      tlr = a->axis.subtic_length;
    
    for (i = 0; i < a->axis.tics; i++) {
      for (j = 1; 
	   (v = a->axis.tic_values[i] + j * stv) < a->axis.max &&
	   v < a->axis.tic_values[i+1];
	   j++, xp++) {
	assert(xp - a->axis.tic_segments < ntic);
	if (a->axis.vertical) {
	    xp->x1 = a->axis.x1 - tll;
	    xp->x2 = a->axis.x2 + tlr;
	    xp->y1 = xp->y2 = AtScaleUserToPixel(a->axis.scale, v);
	  } else {
	    /* Horizontal */
	    xp->y1 = a->axis.y1 + tll;
	    xp->y2 = a->axis.y2 - tlr;
	    xp->x1 = xp->x2 = AtScaleUserToPixel(a->axis.scale, v);
	  }
      }
    }
  }
  /* assert(xp - a->axis.tic_segments == ntic); NOT TRUE as min and
     max may not be multiples of tic_interval */
  
  a->axis.num_tic_segments = (xp - a->axis.tic_segments);
  
  /* Now the grid - calculate it even if we don't use it */
  if (a->axis.vertical) {
    for (xp = a->axis.grid_segments, i = 0;
	 i < a->axis.tics; i++, xp++) {
      xp->y1 = xp->y2 = a->axis.tic_coords[i];
      xp->x1 = a->axis.x1;
      xp->x2 = a->axis.x1 + (a->axis.mirror ? -a->axis.grid_length :
			     a->axis.grid_length);
    }
  } else {
    /* Horizontal */
    for (xp = a->axis.grid_segments, i = 0;
	 i < a->axis.tics; i++, xp++) {
      xp->x1 = xp->x2 = a->axis.tic_coords[i];
      xp->y1 = a->axis.y1;
      xp->y2 = a->axis.y1 - (a->axis.mirror ? -a->axis.grid_length :
			     a->axis.grid_length);
    }
  }
  a->axis.num_grid_segments = xp - a->axis.grid_segments; 
}

 

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

static Boolean AxisSetValues P((AtAxisObject current,
				AtAxisObject request,
				AtAxisObject new));
static Boolean AxisSetValues(current, request, new)
AtAxisObject current, request, new;
{
#define Changed(field) (current->axis.field != new->axis.field)
  Boolean rescale = False;
  Boolean layout = False; 
  Boolean redraw = False;
  Boolean refresh = False;
  Boolean tic_values = False;
  Boolean tic_pixels = False;
  
  /* free/reallocate AtText, FontFamilies, GCs, etc */
  if (Changed(label)) {
    FreeLabel(current);
    CopyLabel(new);
  }

  if (Changed(label_font_family)) {
    FreeLabelFF(new);
    GetLabelFF(new);
  }

  if (Changed(label) || Changed(label_font_family) ||
      Changed(label_size)) {
    FreeLabelText(new);
    GetLabelText(new);
    redraw = True;
  }
    
  if (Changed(label_color)) {
    FreeLabelGC(new);
    GetLabelGC(new);
    refresh = True;
  }

  if (Changed(axis_color)) {
    FreeAxisGC(new);
    GetAxisGC(new);
    refresh = True;
  }

  if (Changed(number_font_family)) {
    FreeNumberFF(new);
    GetNumberFF(new);
    tic_values = True;
    redraw = True;
  }

  if (Changed(number_size)) {
    tic_values = True; 
    redraw = True;
  }

  if (Changed(number_color)) {
    FreeNumberGC(new);
    GetNumberGC(new);
    refresh = True;
  }
    
  if (Changed(tic_density)) {
    if (new->axis.tic_density > 5) new->axis.tic_density = 5;
    if (new->axis.tic_density < -5) new->axis.tic_density = -5;
    if (Changed(tic_density) && new->axis.auto_number)
      tic_values = True;
  }
  
  if (Changed(subtic_density)) {
    if (new->axis.subtic_density > 5) new->axis.subtic_density = 5;
    if (new->axis.subtic_density < -5) new->axis.subtic_density = -5;
    if (Changed(subtic_density) && new->axis.auto_number)
      tic_values = True;
  }

  if (Changed(tic_labels)) {
    FreeUserTicLabels(new);
    ParseUserTicLabels(new);
    tic_values = True;
  }
  
  if (Changed(min) || Changed(max) || Changed(transform))  {
    AtScaleRescale(new->axis.scale, new->axis.min, new->axis.max);
    AtScaleChangeTransform(new->axis.scale, new->axis.transform); 
    CheckScaleValue(new);
    tic_values = True;
    rescale = True;
  }

  if (Changed(round_endpoints) || Changed(auto_number) ||
      Changed(tic_multiplier) || Changed(tic_format))
    tic_values = True;

  if (Changed(subtics) || Changed(tics))
    if (!new->axis.auto_number) tic_values = True;
  
  if (Changed(draw_grid)) redraw = True;

  if (Changed(vertical)) {
    XtAppError(XtWidgetToApplicationContext((Widget)new), 
	       "Not supposed to change an axis vertical resource");
    new->axis.vertical = current->axis.vertical;
  }

  if (Changed(draw_numbers) || Changed(tics_outside) ||
      Changed(tics_inside) || Changed(numbers_outside))
    layout = True;

  if (Changed(tic_length) || Changed(subtic_length))
      tic_pixels = True;
  
  if (tic_values) {
    rescale |= CalculateTicValues(new);
    CalculateTicPixels(new);
    redraw = True;
  } else if (tic_pixels) {
    CalculateTicPixels(new);
    redraw = True;
  }
  
  if (rescale) 
    AtPlotterRescaleRequired((AtPlotWidget)new);
  if (layout)
    AtPlotterLayoutRequired((AtPlotWidget)new);
  if (redraw) 
    AtPlotterRedrawRequired((AtPlotWidget)new);
  else if (refresh)
    AtPlotterRefreshRequired((AtPlotWidget)new);
    
  return False;
#undef Changed    
}

/**********************************************************************
 *
 * The draw routine
 * 
 */
void AtAxisDraw(dpy, win, axis, bg, region)
Display *dpy;
Drawable win;
AtAxisObject axis ;
Pixel bg;
Region region; 
{
  AtAxisPart *a = &axis->axis;
  int i;

  if (region) {
    XSetRegion(dpy, a->axisGC, region);
    XSetRegion(dpy, a->numberGC, region);
    XSetRegion(dpy, a->labelGC, region);
    XSetRegion(dpy, a->dottedGC, region);
  }

  /* draw the lines corresponding to the tics and the axis */
  XDrawSegments(dpy, win, a->axisGC, a->tic_segments,
		a->num_tic_segments);
  
  /* draw the label */
  if (a->label_text) {
    AtTextDraw(dpy, win, a->labelGC, a->label_text, a->label_x,
	       a->label_y);
  }

  /* Draw the grid, if required */
  if (a->draw_grid) {
    XDrawSegments(dpy, win, a->dottedGC, a->grid_segments,
		  a->num_grid_segments);
  }

  /* Draw the numbers, if required */
  if (a->draw_numbers) {
    int tl = 0;
    int x, y;
    
    if (a->numbers_outside && a->tics_outside) tl = a->tic_length;
    if (!a->numbers_outside && a->tics_inside) tl = a->tic_length;
    tl += 2;			/* Margin between tic and label */
    
    for (i = 0; i < a->tics; i++) {
      if (a->vertical) {
	x = a->x1;
	y = a->tic_coords[i];
	if (!a->mirror ^ !!a->numbers_outside)
          x += tl;
        else
          x -= tl + AtTextWidth(a->tic_text[i]);
        y += AtTextHeight(a->tic_text[i])/2;
      } else {
	x = a->tic_coords[i];
        y = a->y1;
        if (!a->mirror ^ !!a->numbers_outside)
          y -= tl + AtTextDescent(a->tic_text[i]);	
        else
          y += tl + AtTextAscent(a->tic_text[i]); 
	if (i == a->tics - 1)
	  /* RHS - right justify it */
	  x -= AtTextWidth(a->tic_text[i]);
	else if (i > 0)
	  /* Middle tics, center them */
	  x -= AtTextWidth(a->tic_text[i])/2;
	else {
	  /* Left hand edge, left justify it. */
	}
      }
      AtTextDraw(dpy, win, a->numberGC, a->tic_text[i], x, y); 
    }
  }
  if (region) {
    XSetClipMask(dpy, a->axisGC, None);
    XSetClipMask(dpy, a->numberGC, None);
    XSetClipMask(dpy, a->labelGC, None);
    XSetClipMask(dpy, a->dottedGC, None);
  }
}

/**********************************************************************/
/*
 * returns the "width" (the height if horizontal) of the passed axis.
 * Only considers that part of the label marked "outside".
 */
int AtAxisWidth P((AtAxisObject w)); 
int AtAxisWidth(w)
AtAxisObject w;
{
  XtCheckSubclass((Widget)w, atAxisObjectClass, 
		  "AtAxisWidth needs an Axis object");
  
  if (w->axis.vertical) {
    return ((w->axis.label_text) ? AtTextWidth(w->axis.label_text) : 0) +
      ((w->axis.draw_numbers && w->axis.numbers_outside) ?
       w->axis.max_num_width : 0) +
	 ((w->axis.tics_outside) ? w->axis.tic_length + 1 : 0) +
	   MIN_MARGIN;
  }
  else {
    return ((w->axis.label_text) ? AtTextHeight(w->axis.label_text) : 0) + 
      ((w->axis.draw_numbers && w->axis.numbers_outside &&
	w->axis.tics > 0) ?
       AtTextHeight(w->axis.tic_text[0]) : 0) +
	 ((w->axis.tics_outside) ? w->axis.tic_length + 1 : 0) +
	   MIN_MARGIN;
  }
}

/**********************************************************************
 *
 * Set the user bounds on the plot.  This must set up enough
 * information that the AtAxisWidth routine returns a sensible answer.
 * We fake a text entry in tic_text[0] if needed, and use the max as a
 * gues as to text width.
 * 
 */

void AtAxisSetBounds(w, min, max)
AtAxisObject w;
double min, max;
{
  AtText *t;
  char lbl[100];
  
  XtCheckSubclass((Widget)w, atAxisObjectClass, 
		  "AtAxisSetBounds needs an AtAxisObject");
  
  w->axis.min = min;
  w->axis.max = max;
  AtScaleRescale(w->axis.scale, min, max);
  CheckScaleValue(w);

  /* Stick in here clever autoscaling heuristics.  None currently. */
  
  /* 
   * Somehow set w->max_num_width - 
   * take length of label at max as a guess.
   */
  sprintf(lbl, w->axis.tic_format, max * w->axis.tic_multiplier);
  t = AtTextCreate(lbl, w->axis.numberFF, w->axis.number_size);
  
  w->axis.max_num_width = AtTextWidth(t);
  if (w->axis.tic_text && w->axis.tics > 0) {
    /* No need to fake it, but use NEW text to get height*/
    AtTextDestroy(w->axis.tic_text[0]);
    w->axis.tic_text[0] = t; 
  } else {
    /* Need to fake tic_text[0] for AtTextWidth */
    FreeTicValueStuff(w); 
    w->axis.tics = 1;
    w->axis.tic_text = (AtText **)XtMalloc(sizeof (AtText *));
    w->axis.tic_text[0] = t;
    w->axis.tic_label_array = (String *)XtMalloc(sizeof (String));
    w->axis.tic_label_array[0] = AtNewString(lbl); 
  }
  
  w->axis.min_max_changed = True; 
}


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

void AtAxisGetBounds(w, min, max)
AtAxisObject w;
double *min, *max;
{
  XtCheckSubclass((Widget)w, atAxisObjectClass, 
		  "AtAxisGetBounds needs an AtAxisObject");
  
  if (min) *min = w->axis.min;
  if (max) *max = w->axis.max;
}

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

void AtAxisSetLocation(w, x1, y1, x2, y2, grid_length)
AtAxisObject w;
int x1, y1, x2, y2, grid_length;
{
  XtCheckSubclass((Widget)w, atAxisObjectClass, 
		  "AtAxisSetLocation needs an AtAxisObject");
  
  w->axis.x1 = x1;
  w->axis.y1 = y1;
  w->axis.x2 = x2;
  w->axis.y2 = y2;
  w->axis.grid_length = grid_length;
  
  if (w->axis.vertical && x1 != x2) {
    XtAppError(XtWidgetToApplicationContext((Widget)w), 
	       "Vertical axis given non-vertical position");
  }
  if (!w->axis.vertical && y1 != y2) {
    XtAppError(XtWidgetToApplicationContext((Widget)w), 
	       "Horizontal axis given non-horizontal position");
  }

  if (w->axis.vertical) AtScaleResize(w->axis.scale, y1, y2);
  else AtScaleResize(w->axis.scale, x1, x2);

  if (w->axis.min_max_changed) {
    (void) CalculateTicValues(w);
    /* Can ignore return here a global rescale is pending if
       AtAxisSetLocation is ever called. */
  }
  w->axis.min_max_changed = False;
  CalculateTicPixels(w);
}

/*****************************************************************
 *
 * AtAxisGetScale
 * return the scale for this axis
 */

AtScale *AtAxisGetScale(a)
AtAxisObject a;
{
  return a->axis.scale;
}

/*****************************************************************
 *
 * AtAxisGetTransform
 * rteurn the current transform for this axis
 */
AtTransform AtAxisGetTransform(a)
     AtAxisObject a;
{
  return a->axis.transform;
}

/*****************************************************************
 *
 * Copy the relevent things about tick spacing and so on to the second
 * axis.  Used for setting up frame axes.  Could possibly be used for
 * linking multiple graphs....
 *
 * Must set up enough info for AtAxisWidth to work.
 */
void AtAxisCopyBounds(old, new)
AtAxisObject old, new;
{
  AtText *t;
  
  XtCheckSubclass((Widget)old, atAxisObjectClass, 
		  "Need Axis Object as old arg in AtAxisCopyBounds");
  XtCheckSubclass((Widget)new, atAxisObjectClass, 
		  "Need Axis Object as new arg in AtAxisCopyBounds");
  
  new->axis.min = old->axis.min;
  new->axis.max = old->axis.max;
  
  t = AtTextCreate(old->axis.tic_label_array[0],
		   new->axis.numberFF, new->axis.number_size);
  new->axis.max_num_width = AtTextWidth(t);

  if (new->axis.tic_text && new->axis.tics > 0) {
    /* No need to fake, but use the NEW text to gt height! */
    AtTextDestroy(new->axis.tic_text[0]);
    new->axis.tic_text[0] = t; 
  } else {
    /* Need to fake tic_text[0] for AtTextWidth */
    FreeTicValueStuff(new); 
    new->axis.tics = 1;
    new->axis.tic_text = (AtText **)XtMalloc(sizeof (AtText *));
    new->axis.tic_text[0] = t;
    new->axis.tic_label_array = (String *)XtMalloc(sizeof (String));
    new->axis.tic_label_array[0] = AtNewString(old->axis.tic_label_array[0]); 
  }
  
  new->axis.min_max_changed = True; 
}

/******************************************************************
 *
 * Copy all the known information about tic spacing, tic labels etc
 * from the first to the second.  Done for frame axes as a prelude to
 * AtAxisSetLocation.
 *
 */
void AtAxisCopyTics(old, new)
     AtAxisObject old, new; 
{
  int i, nt, mw = 0, w;

  XtCheckSubclass((Widget)old, atAxisObjectClass, 
		  "Need Axis Object as old arg in AtAxisCopyTics");
  XtCheckSubclass((Widget)new, atAxisObjectClass, 
		  "Need Axis Object as new arg in AtAxisCopyTics");

  FreeTicValueStuff(new);

  nt = new->axis.tics = old->axis.tics;
  new->axis.subtics = old->axis.subtics;
  new->axis.min = old->axis.min;
  new->axis.max = old->axis.max;
  AtScaleRescale(new->axis.scale, new->axis.min, new->axis.max); 
  new->axis.tic_interval = old->axis.tic_interval;
  
  new->axis.tic_label_array = (String *)XtCalloc(nt, sizeof (String));
  new->axis.tic_text = (AtText **)XtCalloc(nt, sizeof (AtText *));
  new->axis.tic_values = (double *)XtCalloc(nt, sizeof (double));

  for (i = 0; i < nt; i++) {
    new->axis.tic_label_array[i] =
      AtNewString(old->axis.tic_label_array[i]);
    new->axis.tic_values[i] = old->axis.tic_values[i];
    new->axis.tic_text[i] =
      AtTextCreate(new->axis.tic_label_array[i], new->axis.numberFF,
		   new->axis.number_size);
    if ((w = AtTextWidth(new->axis.tic_text[i])) > mw)
      mw = w;
  }
  new->axis.max_num_width = mw;
}

/*********************************************************************
 *
 * The public routine to read and parse date labels....
 * 
 */
void AtAxisDateLabels(a, data, stride, start, num)
     AtAxisObject a;
     String *data;
     int stride, start, num;
{
  int max = 0, next = 0;
  int i;
  
  FreeDateLabelStuff(a);

  if (floor(a->axis.min) != a->axis.min || 
      floor(a->axis.max) != a->axis.max) {
    XtAppError(XtWidgetToApplicationContext((Widget)a), 
	       "AtAxisDateLabels should be called with integer axis bounds");
    return;
  }
  if (a->axis.transform != AtTransformLINEAR) {
    XtAppError(XtWidgetToApplicationContext((Widget)a), 
	       "AtAxisDateLabels should be called with linear axis scale");
    return;
  }
  
  for (i = 0; i < num; i++, data = (String *)((char *)data + stride)) {
    if (*data && **data) {
      if (next >= max) {
	a->axis.date_label_array =
	  (String *)XtRealloc((char *)a->axis.date_label_array, 
			      (max += 10) * sizeof (String));
	a->axis.date_label_values = 
	  (double *)XtRealloc((char *)a->axis.date_label_values, 
			      max * sizeof (double));
      }
      a->axis.date_label_values[next] = i + start;
      a->axis.date_label_array[next++] = AtNewString(*data);
    }
  }
  a->axis.num_date_labels = next; 
}

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

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

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

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

static void AxisError P((AtAxisObject a, char *errortext)); 
static void AxisError(a, errortext)
     AtAxisObject a;
     char *errortext;
{
  if (a->axis.errorCallback != NULL)
    XtCallCallbacks((Widget)a, XtNerrorCallback, (XtPointer)errortext);
  else
    XtAppWarning(XtWidgetToApplicationContext((Widget)a), errortext);
}
/*
 * Local Variables:
 * eval: (set-c-style 'GNU)
 * End:
 */
