static char RCSid[] = "$Id: Plotter.c,v 1.0 91/08/22 15:33:41 gnb Exp $"; 
/*
 * $Source: /export/data/sources/x/At/Plotter/RCS/Plotter.c,v $
 * 
 * $Log:	Plotter.c,v $
 * Revision 1.0  91/08/22  15:33:41  gnb
 * Initial revision
 * 
 * 
 */

/*

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.

*/

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

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>    

#include <X11/IntrinsicP.h>	/* For the XtCheckSubclass def */

#ifdef _AtDevelopment_
#include "AtConverters.h"    
#include "Scale.h"    
#include "PlotterP.h"
#include "Plot.h"
#include "AxisCore.h"
#else
#include <At/AtConverters.h>
#include <At/Scale.h>   
#include <At/PlotterP.h>
#include <At/Plot.h>
#include <At/AxisCore.h>
#endif

    
#define NUMCHILDREN(w) (w->composite.num_children)
#define CHILD(w,i) ((AtPlotWidget)(w->composite.children[i]))
#define CONSTRAINT(w,i) \
     ((AtPlotterConstraints)(((Widget)CHILD(w,i))->core.constraints))
#define CONSTRAINTS(cw) \
     ((AtPlotterConstraints)(((Widget)(cw))->core.constraints))

#define NTHCHILDISDISPLAYED(pw,i) (CONSTRAINT(pw, i)->plotter.displayed)
#define ISDISPLAYED(cw) (CONSTRAINTS(cw)->plotter.displayed)
#define USESX2AXIS(cw) (CONSTRAINTS(cw)->plotter.use_x2_axis)
#define USESY2AXIS(cw) (CONSTRAINTS(cw)->plotter.use_y2_axis)

/* The AtPlotterWidget parent of the child OBJECT cw */
#define ParentPlotter(cw) ((AtPlotterWidget)XtParent((Widget)(cw)))

#define AtNewString(str) str = XtNewString(str)

static void ReRankOrderChildren P((Widget));
static void RankOrderChildren P((Widget));
static void RankOrderRemove P((Widget));

static int RecalcLegend P((AtPlotterWidget w));
static void RedrawLegend P((AtPlotterWidget w, Region r));
    
#define offset(field) XtOffset(AtPlotterWidget,field)
static XtResource plotter_resources[] = {
  {XtNfontFamily, XtCFontFamily, XtRString,
     sizeof(String), offset(plotter.font_family),
     XtRString, "new century schoolbook"},
  {XtNtitle, XtCTitle, XtRString,
     sizeof(String), offset(plotter.title), XtRString, NULL},
  {XtNlegendTitle, XtCLegendTitle, XtRString,
     sizeof(String), offset(plotter.legend_title), XtRString, "Legend"},
  {XtNtitleSize, XtCFontSize, XtRFontSize,
     sizeof(int), offset(plotter.title_size),
     XtRImmediate, (caddr_t)AtFontBIG},
  {XtNtitleStyle, XtCFontStyle, XtRFontStyle,
     sizeof(int), offset(plotter.title_style),
     XtRImmediate, (caddr_t)AtFontPLAIN},
  {XtNlegendTitleSize, XtCFontSize, XtRFontSize,
     sizeof(int), offset(plotter.legend_title_size),
     XtRImmediate, (caddr_t)AtFontNORMAL},
  {XtNlegendTitleStyle, XtCFontStyle, XtRFontStyle,
     sizeof(int), offset(plotter.legend_title_style),
     XtRImmediate, (caddr_t)AtFontPLAIN},
  {XtNlegendSize, XtCFontSize, XtRFontSize,
     sizeof(int), offset(plotter.legend_size),
     XtRImmediate, (caddr_t)AtFontNORMAL},
  {XtNlegendStyle, XtCFontStyle, XtRFontStyle,
     sizeof(int), offset(plotter.legend_style),
     XtRImmediate, (caddr_t)AtFontPLAIN},
  {XtNtitleColor, XtCForeground, XtRPixel, sizeof(Pixel),
     offset(plotter.title_color), XtRString, (caddr_t)XtDefaultForeground},
  {XtNlegendColor, XtCForeground, XtRPixel, sizeof(Pixel),
     offset(plotter.legend_color), XtRString, (caddr_t)XtDefaultForeground},
  {XtNshowLegend, XtCShowLegend, XtRBoolean,
     sizeof(Boolean), offset(plotter.show_legend), 
     XtRImmediate, (caddr_t)True},
  {XtNlegendWidth, XtCLegendWidth, XtRShort,
     sizeof(short), offset(plotter.default_legend_width), 
     XtRImmediate, (caddr_t)0},
  {XtNlegendSpacing, XtCMargin, XtRShort,
     sizeof(short), offset(plotter.legend_spacing), XtRImmediate, (caddr_t) 3},
  {XtNmarginWidth, XtCMargin, XtRShort,
     sizeof(short), offset(plotter.margin_width), XtRImmediate, (caddr_t)3},
  {XtNmarginHeight, XtCMargin, XtRShort,
     sizeof(short), offset(plotter.margin_height), XtRImmediate, (caddr_t)3},
  {XtNrankChildren, XtCRankChildren, XtRBoolean, sizeof(Boolean), 
     offset(plotter.rank_children), XtRImmediate, (caddr_t)False},
  {XtNxAxis, XtCXAxis, XtRWidget, sizeof(Widget),
     offset(plotter.xaxis), XtRImmediate, NULL},
  {XtNyAxis, XtCYAxis, XtRWidget, sizeof(Widget),
     offset(plotter.yaxis), XtRImmediate, NULL},
  {XtNx2Axis, XtCX2Axis, XtRWidget, sizeof(Widget),
     offset(plotter.x2axis), XtRImmediate, NULL},
  {XtNy2Axis, XtCY2Axis, XtRWidget, sizeof(Widget),
     offset(plotter.y2axis), XtRImmediate, NULL}
};
#undef offset

/*
 * The default bounding box is a nonsensical one, with xmin > xmax.
 * Unless it is modified to a sensible value, the plotter will ignore it.
 * This is so that TextPlots (annotations), axes and similar plot types
 * won't be involved in the axis scale computations.
 */
static BoundingBox default_bounding_box = {1.0, 0.0, 0.0, 0.0};

#define offset(field) XtOffset(AtPlotterConstraints,field)
static XtResource constraint_resources[] = {
  {XtNdisplayed, XtCDisplayed, XtRBoolean,
     sizeof(Boolean), offset(plotter.displayed),
     XtRImmediate, (caddr_t)True},
  {XtNlegendName, XtCLegendName, XtRString,
     sizeof(String), offset(plotter.legend_name),
     XtRString, NULL},
  {XtNuseX2Axis, XtCUseX2Axis, XtRBoolean,
     sizeof(Boolean), offset(plotter.use_x2_axis),
     XtRImmediate, (caddr_t)False},
  {XtNuseY2Axis, XtCUseY2Axis, XtRBoolean,
     sizeof(Boolean), offset(plotter.use_y2_axis),
     XtRImmediate, (caddr_t)False},
  {XtNrankOrder, XtCRankOrder, XtRInt, sizeof(int), 
     offset(plotter.rank_order), XtRImmediate, (caddr_t) 0},
};
#undef offset

static void ClassInitialize P(());
static void Initialize P((AtPlotterWidget, AtPlotterWidget));
static void Destroy P((AtPlotterWidget));
static void Resize P((AtPlotterWidget));
static void Realize P((AtPlotterWidget, XtValueMask *,XSetWindowAttributes *));
static void Redisplay P((AtPlotterWidget, XEvent *, Region));
static Boolean SetValues P((AtPlotterWidget, AtPlotterWidget,
			    AtPlotterWidget));
static void InsertChild P((Widget));
static void DeleteChild P((Widget));
static void ConstraintInitialize P((Widget, Widget));
static void ConstraintDestroy P((Widget));
static Boolean ConstraintSetValues P((Widget, Widget, Widget));

#define superclass (&constraintClassRec)

externaldef(compositeclassrec) AtPlotterClassRec atPlotterClassRec = {
  { /******* CoreClassPart *******/
    /* superclass           */  (WidgetClass) superclass,
    /* class_name           */  "AtPlotter",
    /* widget_size          */  sizeof(AtPlotterRec),
    /* class_initialize     */  (XtProc)ClassInitialize,
    /* class_part_initialize*/  NULL,
    /* class_inited         */  FALSE,
    /* initialize           */  (XtInitProc)Initialize,
    /* initialize_hook      */  NULL,
    /* realize              */  (XtRealizeProc)Realize,
    /* actions              */  NULL,
    /* num_actions          */  0,
    /* resources            */  plotter_resources,
    /* num_resources        */  XtNumber(plotter_resources),
    /* xrm_class            */  NULLQUARK,
    /* compress_motion      */  FALSE,
#ifdef X11R4_INTRINSICS
    /* compress_exposure    */  XtExposeCompressMaximal |
                                XtExposeGraphicsExposeMerged,
#else
    /* compress_exposure    */  TRUE,
#endif
    /* compress_enterleave  */  TRUE,
    /* visible_interest     */  FALSE,
    /* destroy              */  (XtWidgetProc)Destroy,
    /* resize               */  (XtWidgetProc)Resize,
    /* expose               */  (XtExposeProc)Redisplay,
    /* set_values           */  (XtSetValuesFunc) SetValues,
    /* set_values_hook      */  NULL,
    /* set_values_almost    */  XtInheritSetValuesAlmost,
    /* get_values_hook      */  NULL,
    /* accept_focus         */  NULL,
    /* version              */  XtVersion,
    /* callback_offsets     */  NULL,
    /* tm_table             */  NULL,
    /* query_geometry       */  NULL,
    /* display_accelerator  */  NULL,
    /* extension            */  NULL
  },
  { /**** CompositeClassPart ****/
    /* geometry_handler     */  NULL,
    /* change_managed       */  NULL,
    /* insert_child         */  (XtWidgetProc)InsertChild,
    /* delete_child         */  (XtWidgetProc)DeleteChild,
    /* extension            */  NULL
  },
  { /**** ConstraintClassPart ****/
    /* resources            */  constraint_resources,
    /* num_resources        */  XtNumber(constraint_resources),
    /* constraint_size      */  sizeof(AtPlotterConstraintsRec),
    /* initialize           */  (XtInitProc) ConstraintInitialize,
    /* destroy              */  (XtWidgetProc) ConstraintDestroy,
    /* set_values           */  (XtSetValuesFunc) ConstraintSetValues,
    /* extension            */  NULL,
  },
  { /**** AtPlotterClassPart ****/
    /* meaningless field    */  0
  }
};

#ifndef HUGE_VAL
#define HUGE_VAL	HUGE
#endif /* HUGE_VAL */

WidgetClass atPlotterWidgetClass = (WidgetClass) &atPlotterClassRec;


static void ClassInitialize()
{
#ifdef X11R4_INTRINSICS
     static CompositeClassExtensionRec ext;
     
     ext.next_extension = NULL;
     ext.record_type = NULLQUARK;
     ext.version = XtCompositeExtensionVersion;
     ext.record_size = sizeof(CompositeClassExtensionRec);
     ext.accepts_objects = True;
     atPlotterClassRec.composite_class.extension = (XtPointer) &ext;
#endif    
     AtRegisterDoubleConverter();
     AtRegisterFontSizeConverter();
     AtRegisterFontStyleConverter();
     *RCSid = *RCSid;		/* Keep gcc quiet */
}

static void GetTitle P((AtPlotterWidget w)); 
static void GetTitle(w)
AtPlotterWidget w;
{
     if (w->plotter.title != NULL) 
	  w->plotter.title_text = 
	       AtTextCreate(w->plotter.title, w->plotter.ff,
			    w->plotter.title_size, w->plotter.title_style);
     else
	  w->plotter.title_text = NULL;
}

static void FreeTitle P((AtPlotterWidget w)); 
static void FreeTitle(w)
AtPlotterWidget w;
{
     if (w->plotter.title_text) AtTextDestroy(w->plotter.title_text);
     w->plotter.title_text = NULL;
}

static void GetLegendTitle P((AtPlotterWidget w)); 
static void GetLegendTitle(w)
AtPlotterWidget w;
{
     if (w->plotter.legend_title != NULL) 
	  w->plotter.legend_title_text = 
	       AtTextCreate(w->plotter.legend_title, w->plotter.ff,
			    w->plotter.legend_title_size,
			    w->plotter.legend_title_style);
     else
	  w->plotter.legend_title_text = NULL;
}

static void FreeLegendTitle P((AtPlotterWidget w)); 
static void FreeLegendTitle(w)
AtPlotterWidget w;
{
     if (w->plotter.legend_title_text) 
	  AtTextDestroy(w->plotter.legend_title_text);
     w->plotter.legend_title_text = NULL;
}



static void GetLegendText P((AtPlotterConstraints c, AtPlotterWidget p));
static void GetLegendText(c, p)
AtPlotterConstraints c;
AtPlotterWidget p;
{
     if (c->plotter.legend_name != NULL) {
	  c->plotter.legend_name = 
	       AtNewString(c->plotter.legend_name); 
	  c->plotter.legend_text =
	       AtTextCreate(c->plotter.legend_name, p->plotter.ff,
			    p->plotter.legend_size, p->plotter.legend_style);
     }
     else
	  c->plotter.legend_text = NULL;
}

static void FreeLegendText P((AtPlotterConstraints cur,
			      AtPlotterConstraints new));
static void FreeLegendText(cur, new)
AtPlotterConstraints cur, new;
{
     if (cur->plotter.legend_name != NULL)
	  XtFree(cur->plotter.legend_name);
     if (new->plotter.legend_text != NULL) {
	  AtTextDestroy(new->plotter.legend_text);
	  new->plotter.legend_text = NULL;
     }
     cur->plotter.legend_name = NULL;
     cur->plotter.legend_text = NULL;
}

/************************************************************************
 *
 *  Initialize
 *     The main widget instance initialization routine.
 *
 ************************************************************************/

static void Initialize P((AtPlotterWidget request,
			  AtPlotterWidget new));
static void Initialize(request, new)
AtPlotterWidget request, new;
{
     XGCValues gcv;
     AtPlotterPart *pp = &new->plotter;
     Boolean mirror;
     
     
     /* make private copies of string resource */
     pp->font_family = AtNewString(pp->font_family);
     pp->title = AtNewString(pp->title);
     pp->legend_title = AtNewString(pp->legend_title);
     
     pp->ff = AtFontFamilyGet(XtDisplay(new), pp->font_family);
     GetTitle(new);
     GetLegendTitle(new);
     
     gcv.foreground = pp->title_color;
     pp->titleGC = XtGetGC((Widget)new, GCForeground, &gcv);
     
     gcv.foreground = pp->legend_color;
     pp->legendGC = XtGetGC((Widget)new, GCForeground, &gcv);
     
     gcv.foreground = new->core.background_pixel ^ pp->title_color;
     gcv.function = GXxor;
     pp->dragGC = XtGetGC((Widget)new, GCForeground | GCFunction, &gcv);
     
     pp->ordered_children = NULL;
     
     pp->rescale_required = pp->layout_required = pp->redraw_required = True;
     pp->expose_requested = True; /* Redraw will come from map */
     pp->in_layout_mode = False;
     
     if (pp->xaxis) {
	  XtCheckSubclass((Widget)pp->xaxis, atAxisCoreWidgetClass,
			  "AtPlotter needs subclass of AtAxisCore as xAxis");
	  XtVaGetValues((Widget)pp->xaxis, XtNmirror, 
			(XtPointer) &mirror, NULL); 
	  if (mirror) {
	       XtAppWarning(XtWidgetToApplicationContext((Widget)new),
			    "XAxis widget should have XtNmirror false");
	       XtVaSetValues((Widget)pp->xaxis, XtNmirror, False, NULL); 
	  }
     }
     if (pp->yaxis) {
	  XtCheckSubclass((Widget)pp->yaxis, atAxisCoreWidgetClass,
			  "AtPlotter needs subclass of AtAxisCore as yAxis");
	  XtVaGetValues((Widget)pp->yaxis, XtNmirror, 
			(XtPointer) &mirror, NULL); 
	  if (mirror) {
	       XtAppWarning(XtWidgetToApplicationContext((Widget)new),
			    "YAxis widget should have XtNmirror false");
	       XtVaSetValues((Widget)pp->yaxis, XtNmirror, False, NULL); 
	  }
     }
     if (pp->x2axis) {
	  XtCheckSubclass((Widget)pp->x2axis, atAxisCoreWidgetClass,
			  "AtPlotter needs subclass of AtAxisCore as x2Axis");
	  XtVaGetValues((Widget)pp->x2axis, XtNmirror, 
			(XtPointer) &mirror, NULL); 
	  if (!mirror) {
	       XtAppWarning(XtWidgetToApplicationContext((Widget)new),
			    "X2Axis widget should have XtNmirror True");
	       XtVaSetValues((Widget)pp->x2axis, XtNmirror, True, NULL); 
	  }
     }
     if (pp->y2axis) {
	  XtCheckSubclass((Widget)pp->y2axis, atAxisCoreWidgetClass,
			  "AtPlotter needs subclass of AtAxisCore as y2Axis");
	  XtVaGetValues((Widget)pp->y2axis, XtNmirror, 
			(XtPointer) &mirror, NULL); 
	  if (!mirror) {
	       XtAppWarning(XtWidgetToApplicationContext((Widget)new),
			    "y2Axis widget should have XtNmirror True");
	       XtVaSetValues((Widget)pp->y2axis, XtNmirror, True, NULL); 
	  }
     }
     
}

/***********************************************************************
 *
 * A set of helper routines for the redraw protocol.
 *
 * The first few of these are local to this file, the last few are the
 * routines that plotwidget children use to request redraws.
 * 
 */

/* Request a synthetic expose event if none has happened yet */
static void RequestSyntheticExpose P((AtPlotterWidget pw)); 
static void RequestSyntheticExpose(pw)
AtPlotterWidget pw;
{
     XExposeEvent ev;
     
     if (!pw->plotter.expose_requested && XtWindow(pw)) {
#ifdef TRACE
	  fprintf(stderr, "<<<<Synth expose sent\n");
#endif 
	  ev.type = Expose;
	  ev.display = XtDisplay(pw);
	  ev.window = XtWindow(pw);
	  ev.x = ev.y = ev.width = ev.height = ev.count = 0; 
	  XSendEvent(XtDisplay(pw), XtWindow(pw), False, 0, (XEvent *)&ev);
	  pw->plotter.expose_requested = True;
     }
}


/* 
 * ExtendedList maintainence.  These are lists of from, to pairs for
 * each plot for areas requiring rescaling (due to plot extend calls).
 * An ExtendedList of from>to implies the whole plot, and these
 * routines take care of deleteing partials if there is a full rescale
 * present. 
 */
#define cwel (CONSTRAINTS(cw)->plotter.extended_list)

static void DestroyExtendedList P((AtPlotWidget cw)); 
static void DestroyExtendedList(cw)
AtPlotWidget cw;
{
     ExtendedList *c, *n;
     for (c = cwel; c; c = n) {
	  n = c->next;
	  XtFree((char *)c);
     }
     cwel = NULL;
}

static void AddExtendedList P((AtPlotWidget cw, int from, int to));
static void AddExtendedList(cw, from, to)
AtPlotWidget cw;
int from, to;
{
     ExtendedList *el = (ExtendedList *)XtMalloc(sizeof (ExtendedList));
     el->from = from;
     el->to = to;
     if (from > to) {
	  /* Is a complete rescale, so forget any partials */
	  DestroyExtendedList(cw); 
     } else if (cwel && cwel->from > cwel->to) {
	  /* A complete rescale already exists, drop this one */
	  XtFree((char *)el);
	  return;
     }
     /* Either this is a complete rescale request or an additional
      *      partial one */
     el->next = cwel;
     cwel = el;
}

#undef cwel

/*
 * Calculate the new raw bounding box for the whole plot.  Return TRUE if
 * it changed, else FALSE.  This is modified by the axes to make the
 * actual bounding box.
 */
static Boolean NewRawBoundingBox P((AtPlotterWidget pw)); 
static Boolean NewRawBoundingBox(pw)
AtPlotterWidget pw;
{
     AtPlotterBoundingBox nbb;
     BoundingBox *cbbp;
     int i;
     Boolean ret;
     
     nbb.xmin = nbb.ymin = nbb.x2min = nbb.y2min = HUGE_VAL;
     nbb.xmax = nbb.ymax = nbb.x2max = nbb.y2max = -HUGE_VAL;
     
     for (i = 0; i < NUMCHILDREN(pw); i++) {
	  if (NTHCHILDISDISPLAYED(pw, i)) {
	       cbbp = &(CONSTRAINT(pw, i)->plotter.bounding_box);
	       if (cbbp->xmin > cbbp->xmax) continue; 
	       /* Is a child w/o boundingbox */
	       if (CONSTRAINT(pw, i)->plotter.use_x2_axis) {
		    nbb.x2max = Max(nbb.x2max, cbbp->xmax);
		    nbb.x2min = Min(nbb.x2min, cbbp->xmin);
	       } else { 
		    nbb.xmax = Max(nbb.xmax, cbbp->xmax);
		    nbb.xmin = Min(nbb.xmin, cbbp->xmin);
	       } 
	       if (CONSTRAINT(pw, i)->plotter.use_y2_axis) {
		    nbb.y2min = Min(nbb.y2min, cbbp->ymin);
		    nbb.y2max = Max(nbb.y2max, cbbp->ymax);
	       } else {
		    nbb.ymin = Min(nbb.ymin, cbbp->ymin);
		    nbb.ymax = Max(nbb.ymax, cbbp->ymax);
	       }
	  }
     }
#define dif(fld) (nbb.fld != pw->plotter.raw_bounding_box.fld)
     ret = dif(xmin) || dif(xmax) || dif(ymin) || dif(ymax) || 
	  dif(x2min) || dif(x2max) || dif(y2min) || dif(y2max);
#undef dif
#ifdef TRACE
     fprintf(stderr, 
	     "NewRawBoundingBox %s: %.1f,%.1f %.1f,%.1f %.1f,%.1f %.1f,%.1f \n", 
	     ret ? "Changed" : "Kept",
	     nbb.xmin, nbb.xmax, nbb.x2min, nbb.x2max, 
	     nbb.ymin, nbb.ymax, nbb.y2min, nbb.y2max);
#endif 
     pw->plotter.raw_bounding_box = nbb;
     return ret;
}

/*
 * Merge the second boundingbox into the first, return True if the bb changed.
 * (These are per-plot BB,s in the constraint record).  This is used
 * by the data extended code.
 * 
 */
static Boolean MergeBoundingBox P((BoundingBox *ob, BoundingBox *nb));
static Boolean MergeBoundingBox(ob, nb)
BoundingBox *ob, *nb;
{
     BoundingBox old;
     Boolean ret;
     
     old = *ob;
     ob->xmax = Max(ob->xmax, nb->xmax);
     ob->xmin = Min(ob->xmin, nb->xmin);
     ob->ymax = Max(ob->ymax, nb->ymax);
     ob->ymin = Min(ob->ymin, nb->ymin);
#define dif(fld) (old.fld != ob->fld)
     ret = dif(xmin) || dif(xmax) || dif(ymin) || dif(ymax);
#undef dif

#ifdef TRACE
     if (ret) {
	  fprintf(stderr, "MergeBoundingBox changed to %.1f,%.1f %.1f,%.1f\n", 
		  ob->xmin, ob->xmax, ob->ymin, ob->ymax); 
     } else {
	  fprintf(stderr, "MergeBoundingBox kept %.1f,%.1f %.1f,%.1f\n", 
		  ob->xmin, ob->xmax, ob->ymin, ob->ymax); 
     }
#endif
     return ret;
}

/*********************************************************************
 * 
 * These routines are called by children as well as the parent to
 * request redrawing as appropriate.
 * 
 */
void AtPlotterPlotExtended(cw, bb, from, to)
AtPlotWidget cw;
BoundingBox *bb;
int from, to;
{
     AtPlotterWidget pw = ParentPlotter(cw);
     Boolean bb_changed;
     
     XtCheckSubclass((Widget)cw, atPlotWidgetClass, 
		     "AtPlotterPlotExtended requires an AtPlot widget");
     
     bb_changed = MergeBoundingBox(&CONSTRAINTS(cw)->plotter.bounding_box, bb);
     
     if (ISDISPLAYED(cw)) {
	  if (bb_changed && NewRawBoundingBox(pw)) {
	       /* Overall bb has changed, so request an overall rescale */
	       pw->plotter.rescale_required = True;
	  } 
	  /* This plot has been extended, but still fits on the graph */
	  AddExtendedList(cw, from, to); 
	  RequestSyntheticExpose(pw); 
     }
}

/*
 * Refresh is set true if fast_update is on and we can erase the old
 * one without having to redraw everything.
 */
void AtPlotterPlotDataChanged(cw, bb, refresh)
AtPlotWidget cw;
BoundingBox *bb;
int refresh; 
{
     AtPlotterWidget pw = ParentPlotter(cw);
     
     XtCheckSubclass((Widget)cw, atPlotWidgetClass, 
		     "AtPlotterDaChanged requires an AtPlot widget");
     
     CONSTRAINTS(cw)->plotter.bounding_box = *bb;
     
     if (ISDISPLAYED(cw)) {
	  if (NewRawBoundingBox(pw)) {
	       /* Overall bb has changed, so request an overall rescale */
	       pw->plotter.rescale_required = True;
	  }
	  /*
	   * This plot has been changed, but overall bb is the same.
	   * Request a redraw and a rescale on the whole of this plot
	   */
	  AddExtendedList(cw, 0, -1);
	  if (refresh) {
	       AtPlotterRefreshRequired(cw); 
	  } else {
	       pw->plotter.redraw_required = True; 
	       RequestSyntheticExpose(pw); 
	  }
     }
}


void AtPlotterRefreshRequired(cw)
AtPlotWidget cw;
{
     if (!XtIsSubclass((Widget)cw, atPlotWidgetClass))
	  XtAppError(XtWidgetToApplicationContext((Widget)cw),
		     "AtPlotterRefreshRequired requires an AtPlot widget");
     
     if (ISDISPLAYED(cw))  {
	  CONSTRAINTS(cw)->plotter.needs_refresh = True;
	  RequestSyntheticExpose(ParentPlotter(cw)); 
     }
}

void AtPlotterRedrawRequired(cw)
AtPlotWidget cw;
{
     if (!XtIsSubclass((Widget)cw, atPlotWidgetClass))
	  XtAppError(XtWidgetToApplicationContext((Widget)cw),
		     "AtPlotterRedrawRequired requires an AtPlot widget");
     
     if (ISDISPLAYED(cw)) {
	  ParentPlotter(cw)->plotter.redraw_required = True; 
	  RequestSyntheticExpose(ParentPlotter(cw));
     }
}

void AtPlotterLayoutRequired(cw)
AtPlotWidget cw;
{
     if (!XtIsSubclass((Widget)cw, atPlotWidgetClass))
	  XtAppError(XtWidgetToApplicationContext((Widget)cw),
		     "AtPlotterLayoutRequired requires an AtPlot widget");
     
     if (ISDISPLAYED(cw)) {
	  ParentPlotter(cw)->plotter.layout_required = True; 
	  RequestSyntheticExpose(ParentPlotter(cw));
     }
}

void AtPlotterRescaleRequired(cw)
AtPlotWidget cw;
{
     if (!XtIsSubclass((Widget)cw, atPlotWidgetClass))
	  XtAppError(XtWidgetToApplicationContext((Widget)cw),
		     "AtPlotterRescaleRequired requires an AtPlot widget");
     
     if (ISDISPLAYED(cw)) {
	  ParentPlotter(cw)->plotter.rescale_required = True; 
	  RequestSyntheticExpose(ParentPlotter(cw));
     }
}

/* Request a complete recalc of just this plot - used by axis code */
void AtPlotterRecalcThisPlot(cw)
AtPlotWidget cw;
{
     AtPlotterWidget pw = ParentPlotter(cw);
     
     XtCheckSubclass((Widget)cw, atPlotWidgetClass, 
		     "AtPlotterDaChanged requires an AtPlot widget");

     if (ISDISPLAYED(cw)) {
	  if (pw->plotter.in_layout_mode) {
	       /* We have to use this one to avoid infinite loops */
	       pw->plotter.rescale_required = True; 
	  } else {
	       AddExtendedList(cw, 0, -1);
	       RequestSyntheticExpose(pw); 
	  }
     }
}

/*
 * These private ones are called from this file and are passed the
 * parent widget
 */
static void RescaleRequired P((AtPlotterWidget pw)); 
static void RescaleRequired(pw)
AtPlotterWidget pw;
{
     pw->plotter.rescale_required = True; 
     RequestSyntheticExpose(pw);
}

static void LayoutRequired P((AtPlotterWidget pw)); 
static void LayoutRequired(pw)
AtPlotterWidget pw;
{
     pw->plotter.layout_required = True; 
     RequestSyntheticExpose(pw);
}

static void RedrawRequired P((AtPlotterWidget pw)); 
static void RedrawRequired(pw)
AtPlotterWidget pw;
{
     pw->plotter.redraw_required = True; 
     RequestSyntheticExpose(pw);
}

/***********************************************************************
 *
 * The guts of the rescale / redraw / relayout code
 *
 * 1) Layout.  Recalculates all the layouts, returns TRUE if the size
 * or pixel position of any of the four axes changed.
 * 
 * XXX - assume the axes know enough to answer AxisWidth sensibly!
 * XXX - also assumes axis.max & axis.min are determined!
 * 
 */
static Boolean Layout P((AtPlotterWidget pw)); 
static Boolean Layout(pw)
AtPlotterWidget pw;
{
     AtPlotterLayout *lp = &pw->plotter.layout;
     AtPlotterPart *pp = &pw->plotter;
     AtPlotterLayout old;
     int xwid, ywid, x2wid, y2wid;
     Boolean changed;
     
     old = pp->layout;
     
     /* The basic layout */
     lp->x1 = pp->margin_width;
     lp->y1 = pp->margin_height;
     lp->x2 = pw->core.width - 1 - pp->margin_width;
     lp->y2 = pw->core.height - 1 - pp->margin_height;
     
     /* Assume legend is on RHS at present */
     if (pp->show_legend) {
	  lp->x2 -= lp->legend_width + pp->margin_width;
	  lp->legend_x = lp->x2 + pp->margin_width;
     }
     
     /* Assume title at top at present */
     if (pp->title_text) {
	  lp->title_y = lp->y1 + AtTextAscent(pp->title_text);
	  lp->y1 += AtTextHeight(pp->title_text) +
	       pp->margin_height;
     }
     
     /* Calculate the "width" of the axes */
     xwid = ywid = x2wid = y2wid = 0;
     
     if (pp->xaxis && ISDISPLAYED(pp->xaxis))
	  xwid = AtAxisWidth(pp->xaxis);
     if (pp->yaxis && ISDISPLAYED(pp->yaxis))
	  ywid = AtAxisWidth(pp->yaxis);
     if (pp->x2axis && ISDISPLAYED(pp->x2axis))
	  x2wid = AtAxisWidth(pp->x2axis);
     if (pp->y2axis && ISDISPLAYED(pp->y2axis))
	  y2wid = AtAxisWidth(pp->y2axis);
     
     lp->y1 += x2wid; 
     lp->y2 -= xwid;
     lp->x1 += ywid;
     lp->x2 -= y2wid;
     
     lp->width = lp->x2 - lp->x1 + 1;
     lp->height = lp->y2 - lp->y1 + 1;

     /* Don't infinite-loop in braindead small windows! */
     if (lp->width < 1) {
	  lp->width = 1;
	  lp->x2 = lp->x1 + 1;
     }
     if (lp->height < 1) {
	  lp->height = 1;
	  lp->y2 = lp->y1 + 1;
     }
     
     if (pp->title_text) {
	  lp->title_x = lp->x1 + 
	       (lp->width - AtTextWidth(pp->title_text)) / 2;
     }
     
     if (pp->show_legend) {
	  lp->legend_y = lp->y1 + (lp->height - lp->legend_height)/2;
     }
     
#define dif(fld) (lp->fld != old.fld)
     changed = dif(x1) || dif(x2) || dif(y1) || dif(y2);
#undef dif
     
#ifdef TRACE
     fprintf(stderr, "In Layout, %s %d,%d to %d,%d\n",
	     changed ? "chose" : "kept",
	     lp->x1, lp->y1, lp->x2, lp->y2);
#endif
     return changed;
}


/*****************************************************************
 *
 * Ask the axes to actually decide on endpoints.  Returns True if any
 * endpoints changed.
 */
static Boolean DecideAxisValues P((AtPlotterWidget));
static Boolean DecideAxisValues(pw)
AtPlotterWidget pw; 
{
     AtPlotterBoundingBox *bbp = &pw->plotter.bounding_box;
     AtPlotterPart *pp = &pw->plotter;
     double dummy_min, dummy_max;
     AtPlotterBoundingBox obb;
     Boolean changed;

#ifdef TRACE
     fprintf(stderr, 
	     "Axis Values: WAS %.1f,%.1f %.1f,%.1f %.1f,%.1f %.1f,%.1f\n",
	     bbp->xmin, bbp->xmax, bbp->ymin, bbp->ymax,
	     bbp->x2min, bbp->x2max, bbp->y2min, bbp->y2max);
#endif
     /* First, make a copy of the raw bounding box as a starting point. */
     obb = pw->plotter.bounding_box;
     pw->plotter.bounding_box = pw->plotter.raw_bounding_box;
     
     /*
      * Is there is some data plot depending on this axis, make sure
      * we have a valid one.  Otherwise, if there is a displayed axis
      * connected to it, do a dummy AskRange so that frame axes can
      * get min/max set.
      */
     if (bbp->xmax > bbp->xmin) {
	  if (pp->xaxis) {
	       AtAxisAskRange(pp->xaxis, &bbp->xmin, &bbp->xmax);
	  } else {
	       XtAppError(XtWidgetToApplicationContext((Widget)pw),
			    "AtPlotter has no X axis defined");
	  } 
     } else if (pp->xaxis && ISDISPLAYED(pp->xaxis))
	  AtAxisAskRange(pp->xaxis, &dummy_min, &dummy_max); 
     if (bbp->ymax > bbp->ymin) {
	  if (pp->yaxis) {
	       AtAxisAskRange(pp->yaxis, &bbp->ymin, &bbp->ymax);
	  } else {
	       XtAppError(XtWidgetToApplicationContext((Widget)pw), 
			    "AtPlotter has no Y axis defined");
	  } 
     } else if (pp->yaxis && ISDISPLAYED(pp->yaxis))
	  AtAxisAskRange(pp->yaxis, &dummy_min, &dummy_max); 

     if (bbp->x2max > bbp->x2min) {
	  if (pp->x2axis) {
	       AtAxisAskRange(pp->x2axis, &bbp->x2min, &bbp->x2max);
	  } else {
	       XtAppError(XtWidgetToApplicationContext((Widget)pw), 
			    "AtPlotter has no X2 axis defined");
	  } 
     } else if (pp->x2axis && ISDISPLAYED(pp->x2axis))
	  AtAxisAskRange(pp->x2axis, &dummy_min, &dummy_max); 

     if (bbp->y2max > bbp->y2min) {
	  if (pp->y2axis) {
	       AtAxisAskRange(pp->y2axis, &bbp->y2min, &bbp->y2max);
	  } else {
	       XtAppError(XtWidgetToApplicationContext((Widget)pw), 
			    "AtPlotter has no Y2 axis defined");
	  } 
     } else if (pp->y2axis && ISDISPLAYED(pp->y2axis))
	  AtAxisAskRange(pp->y2axis, &dummy_min, &dummy_max); 

#define dif(fld) (obb.fld != bbp->fld)
     changed = dif(xmin) || dif (xmax) || dif(x2min) || dif (x2max) ||
	  dif(ymin) || dif (ymax) || dif(y2min) || dif (y2max);
#undef dif
#ifdef TRACE
     fprintf(stderr, 
	     "In DecideAxisValues, %s %.1f,%.1f %.1f,%.1f %.1f,%.1f %.1f,%.1f\n",
	     changed ? "Changed to" : "Kept",
	     bbp->xmin, bbp->xmax, bbp->ymin, bbp->ymax,
	     bbp->x2min, bbp->x2max, bbp->y2min, bbp->y2max); 
#endif
     return changed; 
}

/*
 * Actually redraw the entire plot
 */
static void Redraw P((AtPlotterWidget pw, Region region));
static void Redraw(pw, region)
AtPlotterWidget pw;
Region region;
{
     int i;
     
     /* First, the title */
     if (pw->plotter.title_text) {
	  AtTextDraw(XtDisplay(pw), XtWindow(pw), pw->plotter.titleGC,
		     pw->plotter.title_text, pw->plotter.layout.title_x,
		     pw->plotter.layout.title_y);
     }
     
     /* Now the legend */
     if (pw->plotter.show_legend) RedrawLegend(pw, region);
     
     /*
      * Now the children, either in rank order or birth order
      */
     if (pw->plotter.rank_children) {
	  Rank *rp;
	  
	  for (rp = pw->plotter.ordered_children; rp; rp = rp->next) {
	       if (!ISDISPLAYED(rp->child)) continue;
	       AtPlotDraw(rp->child, XtDisplay(pw), XtWindow(pw), 
			  region, False);
	  }
     } else {
	  for (i = 0; i < NUMCHILDREN(pw); i++) {
	       if (!NTHCHILDISDISPLAYED(pw, i)) continue;
	       AtPlotDraw(CHILD(pw, i), XtDisplay(pw), XtWindow(pw),
			  region, False);
	  }
     }
}

/************************************************************************
 *
 *  Redisplay
 *     General redisplay function called on exposure events.
 *     THIS IS THE ONLY ROUTINE THAT DRAWS ON THE SCREEN!
 *
 *
 * The algorithm:
 *
 * Clear the screen if required.
 * If rescale is required, calculate min/max of axes (so layout knows
 *   how wide axes are); 
 * Do global relayout if required.
 * Do global rescale if required (e.g. if relayout changed pixel size)
 *   Else rescale each plot according to extended_list, if any
 * Then do the redraw, clipped by the region (if any);
 *   Either a full redraw:
 *     title
 *     legend
 *     axes
 *     each displayed child in order(rank or birth)
 *   Or a refresh of each one if requested.
 * Forget all requested redraws/rescales/etc.
 * 
 ************************************************************************/

static void Redisplay P((AtPlotterWidget pw, XEvent *event, Region region));
static void Redisplay(pw, event, region)
AtPlotterWidget pw;
XEvent *event;
Region region;
{
#define ev ((XExposeEvent *)event)  
     Boolean pixels_moved = False;
     Boolean numbers_moved = False;
     Boolean full_refresh = False;
     AtPlotterPart *pp = &pw->plotter; 
     int i;
     
#ifdef TRACE
     fprintf(stderr, 
	     "\nIn redisplay, Reg = %lx event: synth %d x,y = %d,%d w,h = %d,%d\n",
	     region, ev->send_event, ev->x, ev->y, ev->width, ev->height);
     fprintf(stderr, "layout = %d recalc = %d redraw = %d\n",
	     pp->layout_required, pp->rescale_required,
	     pp->redraw_required);
#endif
     
     if (ev->send_event) region = NULL; /* Is Synthetic! */
     
     /* If the event covers (nearly) the whole window, ignore the region
      *      (for speed!) */
     if (region) {
	  if (ev->x < 10 && ev->y < 10 && 
	      ev->width > pw->core.width - 20 &&
	      ev->height > pw->core.height - 20)
	       region = NULL;
	  full_refresh = True;
#ifdef TRACE
	  fprintf(stderr, "full refresh because of region + area\n");
#endif 
     }
     
     
     /* 
      * Come back to here if the length of an axis changed by > 25%
      * and the axis indicates tic_interval et al needs recalculating,
      * or if one of the calc routines has set rescale_required or
      * layout_required (probably an axis because something like the
      * number width has changed).
      */ 
 recalc_again:
     pp->in_layout_mode = True;
     
     if (pp->rescale_required) {
	  numbers_moved |= DecideAxisValues(pw);
     }
     
     if (pp->layout_required) {
	  pixels_moved |= Layout(pw);
     }
     
     if (pixels_moved) {
	  AtPlotterLayout *lp = &pp->layout;
	  Boolean ti_changed = False;

#ifdef TRACE
	  fprintf(stderr, "Setting positions...\n");
#endif 
	  /*
	   *  With the y axes, must swap min and max values as window is
	   * measured 0=top, we want 0 to be bottom.
	   */ 
	  if (pp->xaxis && ISDISPLAYED(pp->xaxis))
	       ti_changed |= 
		    AtAxisSetPosition(pp->xaxis, lp->x1, lp->y2, lp->x2,
				      lp->y2, lp->y2 - lp->y1);
	  if (pp->yaxis && ISDISPLAYED(pp->yaxis))
	       ti_changed |= 
		    AtAxisSetPosition(pp->yaxis, lp->x1, lp->y2, lp->x1,
				      lp->y1, lp->x2 - lp->x1);
	  if (pp->x2axis && ISDISPLAYED(pp->x2axis))
	       ti_changed |= 
		    AtAxisSetPosition(pp->x2axis, lp->x1, lp->y1, lp->x2,
				      lp->y1, lp->y2 - lp->y1);
	  if (pp->y2axis && ISDISPLAYED(pp->y2axis))
	       ti_changed |= 
		    AtAxisSetPosition(pp->y2axis, lp->x2, lp->y2, lp->x2,
				      lp->y1, lp->x2 - lp->x1);
#ifdef TRACE
	  if (ti_changed) {
	       fprintf(stderr, "Redisplay: Axes length changed!\n");
	  }
#endif
	  if (ti_changed) goto recalc_again; 
     }
     
     pp->rescale_required = pp->layout_required = False;
     if (pixels_moved || numbers_moved) {
	  /* Need to rescale the entire graph */
#ifdef TRACE
	  fprintf(stderr, "Rescaling the lot, starting with axes\n");
#endif 
	  /* Must do the axes first, as they may request recalc */
	  if (pp->xaxis && ISDISPLAYED(pp->xaxis))
	       AtPlotRecalc((AtPlotWidget)pp->xaxis, NULL, NULL, 0, 0);
	  if (pp->x2axis && ISDISPLAYED(pp->x2axis))
	       AtPlotRecalc((AtPlotWidget)pp->x2axis, NULL, NULL, 0, 0);
	  if (pp->yaxis && ISDISPLAYED(pp->yaxis))
	       AtPlotRecalc((AtPlotWidget)pp->yaxis, NULL, NULL, 0, 0);
	  if (pp->y2axis && ISDISPLAYED(pp->y2axis))
	       AtPlotRecalc((AtPlotWidget)pp->y2axis, NULL, NULL, 0, 0);

	  if (pp->layout_required || pp->rescale_required) {
#ifdef TRACE
	       fprintf(stderr, "Axis recalc forces goto\n");
#endif
	       goto recalc_again;
	  }
	  
#ifdef TRACE
	  fprintf(stderr, "Now the children\n");
#endif
	  for (i = 0; i < NUMCHILDREN(pw); i++) {
	       AtPlotWidget ch = CHILD(pw, i);

	       if (!ISDISPLAYED(ch)) continue;
	       if (XtIsSubclass((Widget)ch, atAxisCoreWidgetClass)) {
#define	cha (AtAxisCoreWidget)ch
		    if (cha != pp->xaxis && cha != pp->yaxis && 
			cha != pp->x2axis && cha != pp->y2axis) {
			 XtAppWarning(XtWidgetToApplicationContext((Widget)ch),
				      "AtAxis is displayed but not attached"); 
		    }
	       } else
		    AtPlotRecalc(ch, AtAxisGetScale(USESX2AXIS(ch) ? 
						    pp->x2axis : pp->xaxis), 
				 AtAxisGetScale(USESY2AXIS(ch) ? 
						pp->y2axis : pp->yaxis), 
				 0, -1);
	  }
     } else {
	  /* Not entire graph, perhaps individual chunks? */
#ifdef TRACE
	  fprintf(stderr, "Rescaling individual children\n");
#endif
	  for (i = 0; i < NUMCHILDREN(pw); i++) {
	       ExtendedList *ep;
	       
	       if (!NTHCHILDISDISPLAYED(pw, i) || 
		   !(ep = CONSTRAINT(pw, i)->plotter.extended_list))
		    continue;
	       while (ep) {
		    AtPlotRecalc(CHILD(pw, i), 
				 AtAxisGetScale(USESX2AXIS(CHILD(pw, i)) ? 
						pp->x2axis : pp->xaxis), 
				 AtAxisGetScale(USESY2AXIS(CHILD(pw, i)) ? 
						pp->y2axis : pp->yaxis), 
				 ep->from, ep->to);
		    ep = ep->next;
	       }
	       CONSTRAINT(pw, i)->plotter.needs_refresh = True;
	  } 
     }
     
     if (pp->layout_required || pp->rescale_required) {
#ifdef TRACE
	  fprintf(stderr, "something requested changes in recalc"); 
#endif 
	  goto recalc_again;
     }
     
     /* First, clear the screen if that has been requested */
     if (pp->redraw_required || 
	 pp->expose_requested && (pixels_moved || numbers_moved)) {
#ifdef TRACE
	  fprintf(stderr, "Clearing window due to pixel/numbers moved\n");
#endif
	  XClearWindow(XtDisplay(pw), XtWindow(pw));
     }

     if (pp->redraw_required || pixels_moved || numbers_moved || full_refresh) {
#ifdef TRACE
	  fprintf(stderr, "redrawing the lot.\n");
#endif 
	  Redraw(pw, region);		/* Redraw the whole lot */
     } else {
	  /* Perhaps one of the plots wants redrawing */
#ifdef TRACE
	  fprintf(stderr, "refreshing the children.\n");
#endif 
	  if (pp->rank_children) {
	       Rank *rp;
	       
	       for (rp = pp->ordered_children; rp; rp = rp->next) {
		    if (!ISDISPLAYED(rp->child)) continue;
		    if (CONSTRAINTS(rp->child)->plotter.needs_refresh) {
			 AtPlotDraw(rp->child, XtDisplay(pw), XtWindow(pw),
				    region, True);
		    }
	       }
	  } else {
	       for (i = 0; i < NUMCHILDREN(pw); i++) {
		    if (!NTHCHILDISDISPLAYED(pw, i)) continue;
		    if (CONSTRAINT(pw, i)->plotter.needs_refresh) {
			 AtPlotDraw(CHILD(pw, i), XtDisplay(pw), 
				    XtWindow(pw), region, True);
		    }
	       }
	  }
     }
     
     /* Now forget all requests */
     pp->redraw_required = pp->rescale_required =
	  pp->layout_required = False;
     pp->expose_requested = False;
     
     for (i = 0; i < NUMCHILDREN(pw); i++) {
	  AtPlotterConstraints c = CONSTRAINT(pw, i);
	  
	  c->plotter.needs_refresh = False;
	  DestroyExtendedList(CHILD(pw, i));
	  c->plotter.extended_list = NULL;
     }
     pp->in_layout_mode = False;
     
#ifdef TRACE
     fprintf(stderr, "Done redisplay.\n\n");
#endif
}

/************************************************************************
 *
 *  Destroy
 *	Clean up allocated resources when the widget is destroyed.
 *
 ************************************************************************/

static void Destroy (pw)
AtPlotterWidget pw;
{
     AtPlotterPart *pp = &pw->plotter;
     
     /* Free up the private data */
     FreeTitle(pw);
     FreeLegendTitle(pw);
     AtFontFamilyRelease(pp->ff);
     XtReleaseGC((Widget)pw, pp->titleGC);
     XtReleaseGC((Widget)pw, pp->legendGC);
     XtReleaseGC((Widget)pw, pp->dragGC);
     
     /* free our private copies of string resource */
     XtFree(pp->font_family);
     XtFree(pp->title);
     XtFree(pp->legend_title);
     
     /* free the linked list of ordered_children */
     if (pp->ordered_children) { 
	  Rank *tmp = pp->ordered_children;
	  while (tmp->next) {
	       tmp = tmp->next;
	       XtFree((char *)tmp->prev);
	  }
	  XtFree((char *)tmp);
     }
     
     return;
}

/************************************************************************
 *
 *  Resize
 *
 ************************************************************************/

static void Resize P((AtPlotterWidget pw)); 
static void Resize(pw)
AtPlotterWidget pw;
{
     if (XtWindow(pw)) {
	  LayoutRequired(pw);
     }
}

/************************************************************************
 *
 *  Realize
 *
 ***********************************************************************/

static void Realize P((AtPlotterWidget w, XtValueMask *vm,
		       XSetWindowAttributes *wa));
static void Realize(w, vm, wa)
AtPlotterWidget w;
XtValueMask *vm;
XSetWindowAttributes *wa;
{
     (superclass->core_class.realize)((Widget)w, vm, wa);
     (void) NewRawBoundingBox(w);	/* Calculate initial boundingbox */
     (void) RecalcLegend(w); 
}

/************************************************************************
 *
 *  SetValues
 *
 ************************************************************************/

#define Changed(field) (new->plotter.field != current->plotter.field)

static Boolean SetValues(current, request, new)
AtPlotterWidget current, request, new;
{
     Boolean redraw = False;
     Boolean layout = False;
     Boolean recalc_legend = False;
     Boolean rescale = False;
     XGCValues gcv;
     int i;
     AtPlotterPart *pp = &new->plotter;
     Boolean mirror;
     
     if (Changed(rank_children))
	  redraw = True;
     
     if (Changed(font_family)) {
	  XtFree(current->plotter.font_family);
	  pp->font_family = AtNewString(pp->font_family);
	  AtFontFamilyRelease(pp->ff);
	  pp->ff = AtFontFamilyGet(XtDisplay(new),
					    pp->font_family);
     }
     if (!Changed(title) && 
	 (Changed(font_family) || Changed(title_size)) || Changed(title_style))
	  AtTextReformat(pp->title_text,
			 pp->ff, pp->title_size, pp->title_style);
     
     if (!Changed(legend_title) && 
	 (Changed(font_family) || Changed(legend_title_size) ||
	  Changed(legend_title_style))) {
	  AtTextReformat(pp->legend_title_text,
			 pp->ff, pp->legend_title_size, pp->legend_title_style);
	  recalc_legend = True; 
     }
     
     if (Changed(title)) {
	  XtFree(current->plotter.title);
	  pp->title = AtNewString(pp->title);
	  FreeTitle(new);
	  GetTitle(new);
	  layout = True;
	  redraw = True; 
     }
     
     if (Changed(legend_title)) {
	  XtFree(current->plotter.legend_title);
	  pp->title = AtNewString(pp->legend_title);
	  FreeLegendTitle(new);
	  GetLegendTitle(new);
	  redraw = True;
	  recalc_legend = True;
     }
     
     if (Changed(legend_size)) {
	  for (i = 0; i < NUMCHILDREN(new); i++) {
	       AtPlotterConstraints c = CONSTRAINT(new, i);
	       
	       FreeLegendText(c, c);
	       GetLegendText(c, new);
	  }
	  recalc_legend = True;
     }
     
     if (Changed(title_color)) {
	  XtReleaseGC((Widget)new, pp->titleGC);
	  gcv.foreground = pp->title_color; 
	  pp->titleGC = XtGetGC((Widget)new, GCForeground, &gcv);
	  redraw = True;
     }
     
     if (Changed(legend_color)) {
	  XtReleaseGC((Widget)new, pp->legendGC);
	  gcv.foreground = pp->title_color; 
	  pp->legendGC = XtGetGC((Widget)new, GCForeground, &gcv);
	  redraw = True;
     }
     
     if (Changed(show_legend)) {
	  if (pp->show_legend) (void) RecalcLegend(new);
	  layout = redraw = True;
     }
     
     if (pp->show_legend && 
	 (Changed(default_legend_width) || Changed(legend_spacing))) {
	  recalc_legend = layout = redraw = True;
     }
     if (Changed(margin_width) || Changed(margin_height)) {
	  layout = True;
     }

     if (Changed(xaxis)) {
	  if (!XtIsSubclass((Widget)pp->xaxis, atAxisCoreWidgetClass)) {
	       XtAppWarning(XtWidgetToApplicationContext((Widget)new),
			    "AtPlotter needs a subclass of AtAxisCore as xAxis");
	       pp->xaxis = current->plotter.xaxis;
	  } else {
	       XtVaGetValues((Widget)pp->xaxis, XtNmirror, 
			     (XtPointer) &mirror, NULL); 
	       if (mirror) {
		    XtAppWarning(XtWidgetToApplicationContext((Widget)new),
				 "XAxis widget should have XtNmirror false");
		    XtVaSetValues((Widget)pp->xaxis, XtNmirror, False, NULL); 
	       }
	       if (ISDISPLAYED(pp->xaxis)) {
		    layout = rescale = True; 
	       }
	  }
     }
     if (Changed(yaxis)) {
	  if (!XtIsSubclass((Widget)pp->yaxis, atAxisCoreWidgetClass)) {
	       XtAppWarning(XtWidgetToApplicationContext((Widget)new),
			    "AtPlotter needs a subclass of AtAxisCore as yAxis");
	       pp->yaxis = current->plotter.yaxis;
	  } else {
	       XtVaGetValues((Widget)pp->yaxis, XtNmirror, 
			     (XtPointer) &mirror, NULL); 
	       if (mirror) {
		    XtAppWarning(XtWidgetToApplicationContext((Widget)new),
				 "YAxis widget should have XtNmirror false");
		    XtVaSetValues((Widget)pp->yaxis, XtNmirror, False, NULL); 
	       }
	       if (ISDISPLAYED(pp->yaxis)) {
		    layout = rescale = True; 
	       }
	  }
     }
     if (Changed(x2axis)) {
	  if (!XtIsSubclass((Widget)pp->x2axis, atAxisCoreWidgetClass)) {
	       XtAppWarning(XtWidgetToApplicationContext((Widget)new),
			    "AtPlotter needs a subclass of AtAxisCore as x2Axis");
	       pp->x2axis = current->plotter.x2axis;
	  } else {
	       XtVaGetValues((Widget)pp->x2axis, XtNmirror, 
			     (XtPointer) &mirror, NULL); 
	       if (!mirror) {
		    XtAppWarning(XtWidgetToApplicationContext((Widget)new),
				 "X2Axis widget should have XtNmirror True");
		    XtVaSetValues((Widget)pp->x2axis, XtNmirror, True, NULL); 
	       }
	       if (ISDISPLAYED(pp->x2axis)) {
		    layout = rescale = True; 
	       }
	  }
     }
     if (Changed(y2axis)) {
	  if (!XtIsSubclass((Widget)pp->y2axis, atAxisCoreWidgetClass)) {
	       XtAppWarning(XtWidgetToApplicationContext((Widget)new),
			    "AtPlotter needs a subclass of AtAxisCore as y2Axis");
	       pp->y2axis = current->plotter.y2axis;
	  } else {
	       XtVaGetValues((Widget)pp->y2axis, XtNmirror, 
			     (XtPointer) &mirror, NULL); 
	       if (!mirror) {
		    XtAppWarning(XtWidgetToApplicationContext((Widget)new),
				 "y2Axis widget should have XtNmirror True");
		    XtVaSetValues((Widget)pp->y2axis, XtNmirror, True, NULL); 
	       }
	       if (ISDISPLAYED(pp->y2axis)) {
		    layout = rescale = True; 
	       }
	  }
     }
     
     /* Have looked at all the appropriate fields, so do the work! */
     
     if (recalc_legend) {
	  if (RecalcLegend(new)) layout = True;
	  else redraw = True;
     }

     if (rescale) RescaleRequired(new); 
     if (layout) LayoutRequired(new);
     if (redraw) RedrawRequired(new);
     
     return False;
}

#undef Changed

static void InsertChild P((Widget w)); 
static void InsertChild(w)
Widget w;
{
     AtPlotterWidget p = ParentPlotter(w);
     
     /* warn if child of wrong class */
     if (!XtIsSubclass(w, atPlotWidgetClass))
	  XtAppWarning(XtWidgetToApplicationContext(w),
		       "Attempt to add child that is not a subclass of AtPlot");
     
     /* call the superclass's insert_child proc to actually add the child */
     (*superclass->composite_class.insert_child)(w);
     /*
      * maintain this list even if no rank order is requested, so if
      * rank order is wanted later the list is ready.
      */
     RankOrderChildren(w);
     if (XtIsRealized((Widget)p)) {
	  if (RecalcLegend(p)) LayoutRequired(p);
	  if (NewRawBoundingBox(p)) {
	       RescaleRequired(p); 
	  } else {
	       RedrawRequired(p); 
	  }
     }
}

static void DeleteChild P((Widget w)); 
static void DeleteChild(w)
Widget w;
{
     AtPlotterWidget p = ParentPlotter(w);
     
     /* call the superclass's delete_child proc to actually delete the child */
     (*superclass->composite_class.delete_child)(w);
     RankOrderRemove(w);
     if (XtIsRealized((Widget)p)) {
	  if (RecalcLegend(p)) LayoutRequired(p); 
	  if (NewRawBoundingBox(p)) {
	       RescaleRequired(p); 
	  } else {
	       RedrawRequired(p); 
	  }
     }
}

static void ConstraintInitialize(request, new)
Widget request, new;
{
     AtPlotterConstraints c = CONSTRAINTS((AtPlotWidget)new);
     AtPlotterWidget p = ParentPlotter(new);
     
     GetLegendText(c, p);
     c->plotter.bounding_box = default_bounding_box;
     c->plotter.extended_list = NULL;
     c->plotter.needs_refresh = False;
}


static void ConstraintDestroy(w)
Widget w;
{
     AtPlotterConstraints c = CONSTRAINTS((AtPlotWidget)w);
     FreeLegendText(c, c);
     DestroyExtendedList((AtPlotWidget)w); 
}

static Boolean ConstraintSetValues(current, request, new)
Widget current, request, new;
{
#define Changed(field) (newc->plotter.field != curc->plotter.field)    
     AtPlotterConstraints newc = (AtPlotterConstraints)new->core.constraints;
     AtPlotterConstraints curc =(AtPlotterConstraints)current->core.constraints;
     AtPlotterWidget parent = ParentPlotter(new);
     Boolean redraw = False;
     Boolean rescale = False;
     Boolean layout = False;
     
     if (Changed(displayed) || Changed(use_y2_axis) || Changed(use_x2_axis)) {
	  redraw = True;
	  rescale = NewRawBoundingBox(parent);
	  if (Changed(displayed) && XtIsSubclass(new, atAxisCoreWidgetClass)) {
	       layout = rescale = True;
	  }
     }
     
     if (Changed(legend_name)) {
	  FreeLegendText(curc, newc);
	  GetLegendText(newc, parent);
	  layout = RecalcLegend(parent); 
     }
     
     if (Changed(rank_order))  {
	  ReRankOrderChildren(new);
	  if (parent->plotter.rank_children) redraw = True;
     }
     
     if (layout) LayoutRequired(parent);
     if (rescale) RescaleRequired(parent);
     if (redraw) RedrawRequired(parent);
     
#undef Changed
     return False; 
}

/*****************************************************************
 *
 * The routines for handling the legend
 *
 *
 * Recalculate the layout of the legend, return TRUE if the width changed.
 */
static int RecalcLegend P((AtPlotterWidget pw)); 
static int RecalcLegend(pw)
AtPlotterWidget pw;
{
     int i;
     AtPlotterConstraints c;
     int h = 0, w = 0;
     AtPlotterPart *pp = &pw->plotter;
     
     if (pp->show_legend == False) return FALSE;
     
     h = AtTextHeight(pp->legend_title_text);
     h += pp->margin_height;
     
     for(i = 0; i < NUMCHILDREN(pw); i++) {
	  if (!NTHCHILDISDISPLAYED(pw, i)) continue; 
	  c = CONSTRAINT(pw,i);
	  if (c->plotter.legend_text != NULL) {
	       h += AtTextHeight(c->plotter.legend_text) +
		    pp->legend_spacing;
	       w = Max(w, AtTextWidth(c->plotter.legend_text));
	  }
     }
     /* Add the with of the "icon" */
     w += 16 + 2 * pp->margin_width;
     
     w = Max(w,AtTextWidth(pp->legend_title_text));
     
     if (pp->default_legend_width) w = pp->default_legend_width;
     
     i = w != pp->layout.legend_width;
     
     pp->layout.legend_height = h;
     pp->layout.legend_width = w;
     
     return i;
}

static void RedrawLegend P((AtPlotterWidget w, Region region)); 
static void RedrawLegend(w, region)
AtPlotterWidget w;
Region region;
{
     int i;
     AtText *t;
     int y;
     AtPlotterPart *pp = &w->plotter;
     
     if (pp->show_legend == False) return;
     
     if (region) {
	  XSetRegion(XtDisplay(w), pp->legendGC, region);
     } 
     y = pp->layout.legend_y;
     AtTextDrawJustified(XtDisplay(w), XtWindow(w), pp->legendGC,
			 pp->legend_title_text,
			 AtTextJUSTIFY_CENTER, AtTextJUSTIFY_CENTER,
			 pp->layout.legend_x, y,
			 pp->layout.legend_width,
			 AtTextHeight(pp->legend_title_text));
     
     y += AtTextHeight(pp->legend_title_text) + pp->margin_width;
     for(i = 0; i < NUMCHILDREN(w); i++ ) {
	  if (!NTHCHILDISDISPLAYED(w, i)) continue; 
	  t = CONSTRAINT(w,i)->plotter.legend_text;
	  if (t != NULL) {
	       AtTextDraw(XtDisplay(w), XtWindow(w), pp->legendGC, t,
			  pp->layout.legend_x+2*pp->margin_width+16,
			  y + AtTextAscent(t));
	       
	       AtPlotDrawIcon(CHILD(w, i), XtDisplay(w), XtWindow(w), 
			      pp->layout.legend_x, y, 16, 
			      AtTextHeight(t), region);
	       y += AtTextHeight(t) + pp->legend_spacing;
	  }
     }
     if (region) {
	  XSetClipMask(XtDisplay(w), pp->legendGC, None);
     } 
}

/*
 ******************************************************************
 *
 * RankOrderChildren()
 * Sorts the children of Plotter widget on a list according to their
 * Ranking rather than their birth order.  The lowest ranking child is
 * drawn first, whereas, the highest ranking one is drawn last. The
 * highest ranking plot is therfore always visible (never covered by
 * its siblings, if they overlap).  This is useful, for example, if
 * you have several sets of Barcharts that overlap and you want to
 * control which set should be completely visible (in the foreground)
 * at a given time, and in what order the others should cover each
 * other.
 *
 ********************************************************************
 */

#define ORDLIST parent->plotter.ordered_children


static Rank* getnode P((void)); 
static Rank* getnode ()
{
     return ((Rank*) XtMalloc(sizeof(Rank)));
}

static void RankOrderChildren P((Widget w)); 
static void RankOrderChildren(w)
Widget w;
{
     AtPlotterWidget parent = ParentPlotter(w);
     AtPlotterConstraints pcons = (AtPlotterConstraints) w->core.constraints;
     Rank *locate, *newnode;
     Boolean found = False;
     
     if (!XtIsSubclass(w, atPlotWidgetClass)) return; /* Don't rank axes */
     
     if (ORDLIST == NULL) {
	  ORDLIST = getnode();
	  ORDLIST->prev = NULL;
	  ORDLIST->next = NULL;
	  ORDLIST->child = (AtPlotWidget)w;  
	  ORDLIST->rank_order = pcons->plotter.rank_order; 
	  /* higher rank children go on top of lower rank ones.
	   * rankOrder is a constraint resource of Plotter. */
	  
	  return;
     }
     for (locate = ORDLIST; locate != NULL; locate = locate->next)
	  if (pcons->plotter.rank_order < locate->rank_order) { 
	       /* should be inserted right before locate */
	       newnode = getnode(); /* get a new node */ 
	       newnode->child = (AtPlotWidget)w;
	       newnode->rank_order = pcons->plotter.rank_order;
	       newnode->prev = locate->prev;
	       newnode->next = locate;
	       locate->prev = newnode;
	       if (newnode->prev == NULL) /* first on the list */
		    ORDLIST = newnode;
	       else
		    (newnode->prev)->next = newnode;
	       found = True;
	       break;
	  }
     if (!found) { /* Highest order so far, insert at end of list */
	  for (locate=ORDLIST; locate->next != NULL; locate=locate->next)
	       ;
	  newnode = getnode(); /* get a new node */ 
	  newnode->child = (AtPlotWidget)w;
	  newnode->rank_order = pcons->plotter.rank_order;
	  newnode->prev = locate;
	  newnode->next = locate->next;
	  locate->next = newnode;
     }
}

/*
 ***************************************************************
 * 
 *   ReRankOrderChildren
 *   Remove the child whose ranking changed form the 
 *   ordered_children list. Then it will reinsert the removed
 *   child into the list according to its new rankOrder.
 *
 ***************************************************************
 */

static void ReRankOrderChildren P((Widget w)); 
static void ReRankOrderChildren(w)
Widget w;
{
     RankOrderRemove(w); 
     RankOrderChildren(w); 
}

static void RankOrderRemove P((Widget w)); 
static void RankOrderRemove(w)
Widget w;
{
     AtPlotterWidget parent = ParentPlotter(w);
     Rank *locate;
     
     if (!XtIsSubclass(w, atPlotWidgetClass)) return; /* Don't rank axes */
     
     for (locate = ORDLIST; locate != NULL; locate = locate->next)  {
	  if (locate->child == (AtPlotWidget)w) { 
	       if (locate->next)
		    (locate->next)->prev = locate->prev;
	       if (locate->prev == NULL) /* head of list */
		    ORDLIST = locate->next;
	       else
		    (locate->prev)->next = locate->next;
	       XtFree ((char *)locate);
	       break;
	  }
     }
}

#undef ORDLIST
