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

/*

Copyright 1991 by Burdett, Buckeridge & Young Ltd.

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 Burdett, Buckeridge &
Young Ltd. (BBY) not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior
permission.

BBY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
BBY 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.

*/

/*
 * AtBarPlot widget.  It is a subclass of the Splot.  Simple bar
 * plots, nothing fancy.  Can do stacked bar plots with a bit of fudging.
 */

#include <X11/IntrinsicP.h>
#include <math.h>

#ifdef _AtDevelopment_
#include "BarPlotP.h"
#else
#include <At/BarPlotP.h>
#endif

/*
 * Forward declare all the private widgetclass routines
 */
static void BarPlotDraw P((AtPlotWidget, Display *, Window, 
			    Region, int));
static void BarPlotDrawIcon P((AtPlotWidget, Display *, Window,
				int, int, int, int, Region));
static void BarPlotDrawPS P((AtPlotWidget, FILE *, AtScale *, AtScale *));
static void BarPlotDrawIconPS P((AtPlotWidget, FILE *, int, int, int, int));
static void BarPlotRecalc P((AtPlotWidget, AtScale *, AtScale *, 
			       int from, int to));

static void BarPlotClassInit P((void));
static void BarPlotInitialize P((AtBarPlotWidget, AtBarPlotWidget));
static void BarPlotDestroy P((AtBarPlotWidget));
static Boolean BarPlotSetValues P((AtBarPlotWidget, AtBarPlotWidget,
				    AtBarPlotWidget));
     
static void BarPlotRecalcBB P((AtBarPlotWidget w, BoundingBox *));
static void BarPlotAttach P((AtSPlotWidget, BoundingBox *, int));

/*
 * The resources in this widget
 */
static double one = 1.0, zero = 0.0;
#define off(fld) XtOffsetOf(AtBarPlotRec, barplot.fld)

static XtResource resources[] = {
  {
    XtNcellWidth, XtCCellWidth, XtRDouble, sizeof (double),
    off(cell_width), XtRDouble, (XtPointer)&one
  },
  {
    XtNcellOffset, XtCCellOffset, XtRDouble, sizeof (double),
    off(cell_offset), XtRDouble, (XtPointer)&zero
  },
  {
    XtNdoOutline, XtCDoOutline, XtRBoolean, sizeof (Boolean),
    off(do_outline), XtRImmediate, (XtPointer) False
  },
  {
    XtNdoFill, XtCDoFill, XtRBoolean, sizeof (Boolean),
    off(do_fill), XtRImmediate, (XtPointer) True
  },
  {
    XtNzeroMin, XtCZeroMin, XtRBoolean, sizeof (Boolean),
    off(zero_min), XtRImmediate, (XtPointer) True
  },
  {
    XtNfillColor, XtCForeground, XtRPixel, sizeof (Pixel),
    off(fill_color), XtRString, XtDefaultForeground
  },
  {
    XtNshading, XtCShading, XtRShading, sizeof (AtShading),
    off(shading), XtRImmediate, (XtPointer) AtGRAY0
  },
  {
    XtNscreenShade, XtCScreenShade, XtRBoolean, sizeof (Boolean),
    off(screen_shade), XtRImmediate, (XtPointer) True
  }
};
#undef off

AtBarPlotClassRec atBarPlotClassRec = {
  { /* core fields */
    /* superclass		*/	(WidgetClass) &atSPlotClassRec,
    /* class_name		*/	"AtBarPlot",
    /* widget_size		*/	sizeof(AtBarPlotRec),
    /* class_initialize		*/	(XtProc) BarPlotClassInit,
    /* class_part_initialize	*/	NULL,
    /* class_inited		*/	FALSE,
    /* initialize		*/	(XtInitProc) BarPlotInitialize,
    /* initialize_hook		*/	NULL,
    /* pad			*/	NULL,
    /* pad			*/	NULL,
    /* pad			*/	0,
    /* resources		*/	resources,
    /* num_resources		*/	XtNumber(resources),
    /* xrm_class		*/	NULLQUARK,
    /* pad			*/	FALSE,
    /* pad			*/	FALSE,
    /* pad			*/	FALSE,
    /* pad			*/	FALSE,
    /* destroy			*/	(XtWidgetProc) BarPlotDestroy,
    /* pad			*/	NULL,
    /* pad			*/	NULL,
    /* set_values		*/	(XtSetValuesFunc) BarPlotSetValues,
    /* set_values_hook		*/	NULL,
    /* pad			*/	NULL,
    /* get_values_hook		*/	NULL,
    /* pad			*/	NULL,
    /* version			*/	XtVersion,
    /* callback_private		*/	NULL,
    /* pad			*/	NULL,
    /* pad			*/	NULL,
    /* pad			*/	NULL,
    /* pad			*/	NULL
  },
  { /* atPlot fields */
    /* draw			*/	BarPlotDraw,
    /* draw_icon		*/	BarPlotDrawIcon,
    /* drawPS			*/	BarPlotDrawPS,
    /* draw_iconPS		*/	BarPlotDrawIconPS,
    /* recalc			*/	BarPlotRecalc
  },
  { /* atSPlot fields */
    /* attach_data		*/	BarPlotAttach,
  },
  { /* barPlot fields */
    /* empty			*/	0
  }
};

WidgetClass atBarPlotWidgetClass = (WidgetClass)&atBarPlotClassRec;

/*****************************************************************
 *
 * The init/setvalues procs
 */

static void BarPlotClassInit()
{
     AtRegisterShadingConverter();
     *RCSid = *RCSid;		/* Keep gcc quiet */
}

/*
 * Set up the fillGC
 */
static void GetFillGC P((AtBarPlotWidget));
static void GetFillGC(bg)
AtBarPlotWidget bg;
{
     XGCValues v;
     int mask = GCForeground | GCBackground;
     
     v.foreground = bg->barplot.fill_color;
     v.background = bg->plot.background;
     
     if (bg->barplot.shading != AtGRAY0 && bg->barplot.screen_shade) {
	  v.tile = bg->barplot.shading_pixmap =
	       AtShadingGetPixmap(XtScreenOfObject((Widget)bg),
				  bg->barplot.shading,
				  bg->barplot.fill_color, bg->plot.background);
	  v.fill_style = FillTiled; 
	  mask |= GCTile | GCFillStyle;
     } else {
	  mask |= GCFillStyle;
	  v.fill_style = FillSolid;
     }
     bg->barplot.fill_gc = XtGetGC(XtParent((Widget)bg), mask, &v);
}

static void FreeFillGC P((AtBarPlotWidget));
static void FreeFillGC(bg)
AtBarPlotWidget bg;
{
     if (bg->barplot.shading_pixmap != None)
	  AtShadingReleasePixmap(bg->barplot.shading_pixmap);
     bg->barplot.shading_pixmap = None;
     XtReleaseGC(XtParent((Widget)bg), bg->barplot.fill_gc);
}

/*
 * Initialize
 */
static void BarPlotInitialize(req, new)
AtBarPlotWidget req, new;
{
     if (new->barplot.cell_width <= 0 || new->barplot.cell_width > 1) {
	  XtAppWarning(XtWidgetToApplicationContext(XtParent((Widget)new)),
		       "BarPlot cellWidth resource must be between 0 and 1");
	  new->barplot.cell_width = 1.0;
     }
     if (new->barplot.cell_offset < -1.0 || new->barplot.cell_offset > 1.0) {
	  XtAppWarning(XtWidgetToApplicationContext(XtParent((Widget)new)),
		       "BarPlot cellOffset resource must be between -1.0 and 1.0");
	  new->barplot.cell_offset = 0.0;
     }
     if (!new->barplot.do_fill && !new->barplot.do_outline) {
	  XtAppWarning(XtWidgetToApplicationContext(XtParent((Widget)new)),
		       "BarPlot must have either doFill or doOutline");
	  new->barplot.do_fill = True;
     }
     new->barplot.shading_pixmap = None; /* Avoid bogus frees */
     GetFillGC(new);
     new->barplot.fill_rectangles = NULL;
}

/*
 * SetValues
 */
static Boolean BarPlotSetValues(old, req, new)
AtBarPlotWidget old, req, new;
{
#define Changed(fld) (old->barplot.fld != new->barplot.fld)
     Boolean recalc = False;
     Boolean redraw = False;
     
     if (new->barplot.cell_width <= 0 || new->barplot.cell_width > 1) {
	  XtAppWarning(XtWidgetToApplicationContext(XtParent((Widget)new)),
		       "BarPlot cellWidth resource must be between 0 and 1");
	  new->barplot.cell_width = 1.0;
     }
     if (new->barplot.cell_offset < -1.0 || new->barplot.cell_offset > 1.0) {
	  XtAppWarning(XtWidgetToApplicationContext(XtParent((Widget)new)),
		       "BarPlot cellOffset resource must be between -1.0 and 1.0");
	  new->barplot.cell_offset = 0.0;
     }
     
     if (Changed(cell_width) || Changed(cell_offset)) {
	  redraw = recalc = True;
     }
     if (Changed(do_fill) || Changed(do_outline)) {
	  if (!new->barplot.do_fill && !new->barplot.do_outline) {
	       XtAppWarning(XtWidgetToApplicationContext(XtParent((Widget)new)),
			    "BarPlot must have either doFill or doOutline");
	       new->barplot.do_fill = True;
	  }
	  redraw = True;
     }
     
     if (Changed(zero_min)) {
	  BoundingBox bb;
	  bb = new->barplot.saved_bb; /* No auto inititialization! */
	  BarPlotRecalcBB(new, &bb);
	  AtPlotterPlotDataChanged((AtPlotWidget)new, &bb, False); 
     }
     
     if (Changed(fill_color) || Changed(shading) || Changed(screen_shade)) {
	  FreeFillGC(new);
	  GetFillGC(new);
	  redraw = True;
     }
     
     if (redraw)
	  AtPlotterRedrawRequired((AtPlotWidget)new);
     if (recalc)
	  AtPlotterRecalcThisPlot((AtPlotWidget)new);
     return False;
}
#undef Changed

/*
 * Destroy
 */
static void BarPlotDestroy(bg)
AtBarPlotWidget bg;
{
     FreeFillGC(bg);
     XtFree((char *)bg->barplot.fill_rectangles); 
}

/*
 * Given the passed bar plot and bounding box, extract the saved
 * bounding box and ajdust it according to the resources.
 */
static void BarPlotRecalcBB(w, bbp)
AtBarPlotWidget w;
BoundingBox *bbp;
{
     bbp->xmin += w->barplot.cell_offset; 
     bbp->xmax += w->barplot.cell_width + w->barplot.cell_offset;
     if (w->barplot.zero_min) {
	  bbp->ymin = Min(bbp->ymin, 0);
	  bbp->ymax = Max(0, bbp->ymax); 
     }
}


/*****************************************************************
 *
 * These routines are the ones called by the parent plot widget
 */
#define bp  (&((AtBarPlotWidget)self)->barplot)
#define bpw ((AtBarPlotWidget)self)
#define PIX ((XRectangle *)bpw->splot.pix)
/* NB PIX is not an lvalue (at least to some picky compilers) */

/*****************************************************************
 *
 * Needs to save the bounding box because the zeroMin resoucre changes
 * how we interpret it.  Also neet to allocate memory.
 */
static void BarPlotAttach(self, bbp, extending)
AtSPlotWidget self;
BoundingBox *bbp;
int extending;
{
     if (extending) {
	  bpw->splot.pix = XtRealloc((char *)PIX, 
				     bpw->splot.num_points * sizeof (XRectangle));
	  bp->fill_rectangles = (XRectangle *) 
	       XtRealloc((char *)bp->fill_rectangles, 
			 bpw->splot.num_points * sizeof (XRectangle));
	  bp->saved_bb.xmax = 
	       Max(bp->saved_bb.xmax, bbp->xmax);
	  bp->saved_bb.ymax = 
	       Max(bp->saved_bb.ymax, bbp->ymax);
	  bp->saved_bb.xmin = 
	       Min(bp->saved_bb.xmin, bbp->xmin);
	  bp->saved_bb.ymin = 
	       Min(bp->saved_bb.ymin, bbp->ymin);
     } else {
	  bp->saved_bb = *bbp;
	  bpw->splot.pix = XtMalloc(bpw->splot.num_points * sizeof (XRectangle));
	  XtFree((char *)bp->fill_rectangles);
	  bp->fill_rectangles = (XRectangle *)XtMalloc(bpw->splot.num_points * 
						       sizeof (XRectangle));
     }
     BarPlotRecalcBB(bpw, bbp);
}

/*
 * Draw the line clipped by the given region.
 */
static void BarPlotDraw(self, dpy, win, region, refresh)
AtPlotWidget self;
Display *dpy;
Window win;
Region region;
int refresh; 
{
#ifdef TRACE
     fprintf(stderr, "BarPlotDraw, %d rectangles\n",
	     bpw->splot.num_points);
#endif 
     if (bpw->splot.old_pix) {
	  if (bpw->plot.fast_update && refresh) {
	       /* 
		* We are in fast update mode, doing a refresh and have old
		* pix stuff, so draw them to "erase" the old s first
		*/
	       /* Do in reverse order to below!! */
	       if (bp->do_outline)
		    XDrawRectangles(dpy, win, bpw->plot.gc,
				    (XRectangle *)bpw->splot.old_pix,
				    bpw->splot.old_num_points);
	       if (bp->do_fill)
		    XFillRectangles(dpy, win, bp->fill_gc, 
				    (XRectangle *)bpw->splot.old_pix,
				    bpw->splot.old_num_points);
	  }
	  XtFree((char *)bpw->splot.old_pix);
	  bpw->splot.old_pix = NULL;
	  bpw->splot.old_num_points = 0;
     }
     
     /* Do the centre first, then outline */
     if (bp->do_fill) {
	  if (region) XSetRegion(dpy, bp->fill_gc, region);
	  XFillRectangles(dpy, win, bp->fill_gc,
			  bp->do_outline ? bp->fill_rectangles : PIX,
			  bpw->splot.num_points);
	  if (region) XSetClipMask(dpy, bp->fill_gc, None);
     }
     if (bp->do_outline) {
	  if (region) XSetRegion(dpy, bpw->plot.gc, region);
	  XDrawRectangles(dpy, win, bpw->plot.gc, PIX,
			  bpw->splot.num_points);
	  if (region) XSetClipMask(dpy, bpw->plot.gc, None);
     }
}

/*
 * draw the "icon" in the given place.
 */
static void BarPlotDrawIcon(self, dpy, win, x1, y1, width, height, region)
AtPlotWidget self;
Display *dpy;
Window win;
int x1, y1, width, height;
Region region;
{
     if (bp->do_fill) {
	  if (region) XSetRegion(dpy, bp->fill_gc, region);
	  XFillRectangle(dpy, win, bp->fill_gc, x1, y1, width, height);
	  if (region) XSetClipMask(dpy, bp->fill_gc, None);
     }
     
     if (bp->do_outline) {
	  if (region) XSetRegion(dpy, bpw->plot.gc, region);
	  XDrawRectangle(dpy, win, bpw->plot.gc, x1, y1, width, height);
	  if (region) XSetClipMask(dpy, bpw->plot.gc, None);
     }
}

/*
 * postscript stuff
 */
static void BarPlotDrawPS(self, fp, xs, ys)
AtPlotWidget self;
FILE *fp;
AtScale *xs, *ys; 
{
     int i, y0pix;
     char term[100];
     char *shade = AtShadingPS(bp->shading);
     
     fprintf(fp, "%%BeginObject: BarPlot\nGS\n");
     AtPlotPSLineStyle(fp, (AtPlotWidget)bpw);
     
     if (AtScaleGetLow(ys) < 0 && AtScaleGetHigh(ys) > 0) {
	  y0pix = AtScaleUserToPixel(ys, 0.0);
     } else if (AtScaleGetLow(ys) < 0) {
	  y0pix = AtScaleGetHighPix(ys);
     } else {
	  y0pix = AtScaleGetLowPix(ys);
     } 
     
     if (bp->do_fill) {
	  if (bp->do_outline)
	       sprintf(term, "GS %s fill GR ST", shade);
	  else
	       sprintf(term, "%s fill", shade);
     } else strcpy(term, "ST");
     
     for (i = 0; i < bpw->splot.num_points; i++) {
	  int x0pix, x1pix, y1pix;
	  
	  x0pix = 
	       AtScaleUserToPixel(xs, i + bpw->splot.start + bp->cell_offset);
	  x1pix = 
	       AtScaleUserToPixel(xs, i + bpw->splot.start + bp->cell_offset
				  + bp->cell_width);
	  y1pix = AtScaleUserToPixel(ys, AtSPlotGetValue((AtSPlotWidget)bpw, i));
	  
	  fprintf(fp, "%d %d M %d %d L %d %d L %d %d L CP %s\n",
		  x0pix, y0pix, x0pix, y1pix, x1pix, y1pix, x1pix, y0pix, term);
     }
     fprintf(fp, "GR\n%% EndObject\n");
}


static void BarPlotDrawIconPS(self, fp, x1, y1, width, height)
AtPlotWidget self;
FILE *fp;
int x1, y1, width, height;
{
     char term[50], *shade = AtShadingPS(bp->shading);
     
     if (bp->do_fill) {
	  if (bp->do_outline)
	       sprintf(term, "GS %s fill GR ST", shade);
	  else
	       sprintf(term, "%s fill", shade);
     } else strcpy(term, "ST");
     
     
     fprintf(fp, "GS "); 
     AtPlotPSLineStyle(fp, (AtPlotWidget)bpw);
     
     fprintf(fp, " %d %d M %d 0 RL 0 %d RL %d 0 RL CP %s GR\n", 
	     x1, y1, width, -height, -width, term);
}


/*
 * Recalc the data according to the passed x and y scales
 */
static void BarPlotRecalc(self, xs, ys, from, to)
AtPlotWidget self;
AtScale *xs, *ys; 
int from, to;
{
     int i;
     int y0pix, xpix, hipix, wid;
     XRectangle *rp;
     
#ifdef TRACE
     fprintf(stderr, "BarPlotRecalc from %d to %d\n", from, to);
#endif 
     if (from > to) {
	  from = 0;
	  to = bpw->splot.num_points - 1;
     }
     
     if (AtScaleGetLow(ys) < 0 && AtScaleGetHigh(ys) > 0) {
	  y0pix = AtScaleUserToPixel(ys, 0.0);
     } else if (AtScaleGetLow(ys) < 0) {
	  y0pix = AtScaleGetHighPix(ys);
     } else {
	  y0pix = AtScaleGetLowPix(ys);
     } 

     rp = &bp->fill_rectangles[from];
     wid = Max(self->plot.line_width, 1);
     
     for (i = from; i <= to; i++, rp++) {
	  PIX[i].x = xpix = 
	       AtScaleUserToPixel(xs, (double)(i + bpw->splot.start) +
				  bp->cell_offset);
	  PIX[i].y = hipix = 
	       AtScaleUserToPixel(ys, AtSPlotGetValue((AtSPlotWidget)bpw, i));
	  PIX[i].width = 
	       AtScaleUserToPixel(xs, bp->cell_width + bp->cell_offset +
				  (double)(i + bpw->splot.start)) - xpix;
	  PIX[i].height = hipix > y0pix ? hipix - y0pix : y0pix - hipix;
	  /* Now make the fill rectangles 1 pix smaller each edge */
	  rp->x = xpix + wid;
	  rp->y = hipix + wid;
	  rp->width = Max((int)PIX[i].width - wid, 0);
	  rp->height = Max((int)PIX[i].height - wid, 0);
     }
} 
#undef bg
