Blob Blame History Raw
/*
 * $Id: boundary.c,v 1.50 2017/03/09 07:10:52 sfeam Exp $
 */

/* GNUPLOT - boundary.c */

/*[
 * Copyright 1986 - 1993, 1998, 2004   Thomas Williams, Colin Kelley
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose with or 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.
 *
 * Permission to modify the software is granted, but not the right to
 * distribute the complete modified source code.  Modifications are to
 * be distributed as patches to the released version.  Permission to
 * distribute binaries produced by compiling modified sources is granted,
 * provided you
 *   1. distribute the corresponding source modifications from the
 *    released version in the form of a patch file along with the binaries,
 *   2. add special version identification to distinguish your version
 *    in addition to the base release version number,
 *   3. provide your name and address as the primary contact for the
 *    support of your modified version, and
 *   4. retain our contact information in regard to use of the base
 *    software.
 * Permission to distribute the released version of the source code along
 * with corresponding source modifications in the form of a patch file is
 * granted with same provisions 2 through 4 for binary distributions.
 *
 * This software is provided "as is" without express or implied warranty
 * to the extent permitted by applicable law.
]*/

#include "graphics.h"
#include "boundary.h"
#include "alloc.h"
#include "axis.h"
#include "misc.h"
#include "pm3d.h"	/* for is_plot_with_palette */

#define ERRORBARTIC GPMAX((t->h_tic/2),1)

/*{{{  local variables */
static int xlablin, x2lablin, ylablin, y2lablin, titlelin, xticlin, x2ticlin;

/*{{{  local and global variables */
static int key_sample_width;	/* width of line sample */
static int key_sample_height;	/* sample itself; does not scale with "set key spacing" */
static int key_sample_left;	/* offset from x for left of line sample */
static int key_sample_right;	/* offset from x for right of line sample */
static int key_text_left;	/* offset from x for left-justified text */
static int key_text_right;	/* offset from x for right-justified text */
static int key_size_left;	/* size of left bit of key (text or sample, depends on key->reverse) */
static int key_size_right;	/* size of right part of key (including padding) */
static int key_xleft;		/* Amount of space on the left required by the key */
static int max_ptitl_len = 0;	/* max length of plot-titles (keys) */
static int ptitl_cnt;		/* count keys with len > 0  */

static int key_width;		/* calculate once, then everyone uses it */
static int key_height;		/* ditto */
static int key_title_height;	/* nominal number of lines * character height */
static int key_title_extra;	/* allow room for subscript/superscript */
static int time_y, time_x;
static int title_x, title_y;

/*
 * These quantities are needed in do_plot() e.g. for histogtram title layout
 */
int key_entry_height;		/* bigger of t->v_char, t->v_tic */
int key_point_offset;		/* offset from x for point sample */
int key_col_wth, yl_ref;
int ylabel_x, y2label_x, xlabel_y, x2label_y;
int x2label_yoffset;
int ylabel_y, y2label_y, xtic_y, x2tic_y, ytic_x, y2tic_x;
int key_rows;
int key_cols;

/*{{{  boundary() */
/* borders of plotting area
 * computed once on every call to do_plot
 *
 * The order in which things are done has become critical:
 *  plot_bounds.ytop depends on title, x2label
 *  plot_bounds.ybot depends on key, if "under"
 *  once we have these, we can setup the y1 and y2 tics and the
 *  only then can we calculate plot_bounds.xleft and plot_bounds.xright
 *  plot_bounds.xright depends also on key RIGHT
 *  then we can do x and x2 tics
 *
 * For set size ratio ..., everything depends on everything else...
 * not really a lot we can do about that, so we lose if the plot has to
 * be reduced vertically. But the chances are the
 * change will not be very big, so the number of tics will not
 * change dramatically.
 *
 * Margin computation redone by Dick Crawford (rccrawford@lanl.gov) 4/98
 */

void
boundary(struct curve_points *plots, int count)
{
    int yticlin = 0, y2ticlin = 0;
    legend_key *key = &keyT;

    struct termentry *t = term;
    int can_rotate = (*t->text_angle) (TEXT_VERTICAL);

    int xtic_textheight=0;	/* height of xtic labels */
    int x2tic_textheight=0;	/* height of x2tic labels */
    int title_textheight=0;	/* height of title */
    int xlabel_textheight=0;	/* height of xlabel */
    int x2label_textheight=0;	/* height of x2label */
    int ylabel_textwidth=0;	/* width of (rotated) ylabel */
    int y2label_textwidth=0;	/* width of (rotated) y2label */
    int timelabel_textwidth=0;	/* width of timestamp */
    int timelabel_textheight=0;	/* height of timestamp */
    int ytic_textwidth=0;	/* width of ytic labels */
    int y2tic_textwidth=0;	/* width of y2tic labels */
    int x2tic_height=0;		/* 0 for tic_in or no x2tics, ticscale*v_tic otherwise */
    int xtic_textwidth=0;	/* amount by which the xtic label protrude to the right */
    int xtic_height=0;
    int ytic_width=0;
    int y2tic_width=0;
    int ttic_textheight=0;	/* vertical clearance for ttics */

    /* figure out which rotatable items are to be rotated
     * (ylabel and y2label are rotated if possible) */
    int vertical_timelabel = can_rotate ? timelabel.rotate : 0;
    int vertical_xtics  = can_rotate ? axis_array[FIRST_X_AXIS].tic_rotate : 0;
    int vertical_x2tics = can_rotate ? axis_array[SECOND_X_AXIS].tic_rotate : 0;
    int vertical_ytics  = can_rotate ? axis_array[FIRST_Y_AXIS].tic_rotate : 0;
    int vertical_y2tics = can_rotate ? axis_array[SECOND_Y_AXIS].tic_rotate : 0;

    TBOOLEAN shift_labels_to_border = FALSE;

    xticlin = ylablin = y2lablin = xlablin = x2lablin = titlelin = 0;

    /*{{{  count lines in labels and tics */
    if (title.text)
	label_width(title.text, &titlelin);
    if (axis_array[FIRST_X_AXIS].label.text)
	label_width(axis_array[FIRST_X_AXIS].label.text, &xlablin);

    /* This should go *inside* label_width(), but it messes up the key title */
    /* Imperfect check for subscripts or superscripts */
    if ((term->flags & TERM_ENHANCED_TEXT) && axis_array[FIRST_X_AXIS].label.text
	&& strpbrk(axis_array[FIRST_X_AXIS].label.text, "_^"))
	    xlablin++;

    if (axis_array[SECOND_X_AXIS].label.text)
	label_width(axis_array[SECOND_X_AXIS].label.text, &x2lablin);
    if (axis_array[FIRST_Y_AXIS].label.text)
	label_width(axis_array[FIRST_Y_AXIS].label.text, &ylablin);
    if (axis_array[SECOND_Y_AXIS].label.text)
	label_width(axis_array[SECOND_Y_AXIS].label.text, &y2lablin);

    if (axis_array[FIRST_X_AXIS].ticmode) {
	label_width(axis_array[FIRST_X_AXIS].formatstring, &xticlin);
	/* Reserve room for user tic labels even if format of autoticks is "" */
	if (xticlin == 0 && axis_array[FIRST_X_AXIS].ticdef.def.user)
	    xticlin = 1;
    }

    if (axis_array[SECOND_X_AXIS].ticmode)
	label_width(axis_array[SECOND_X_AXIS].formatstring, &x2ticlin);
    if (axis_array[FIRST_Y_AXIS].ticmode)
	label_width(axis_array[FIRST_Y_AXIS].formatstring, &yticlin);
    if (axis_array[SECOND_Y_AXIS].ticmode)
	label_width(axis_array[SECOND_Y_AXIS].formatstring, &y2ticlin);
    /*}}} */

    /*{{{  preliminary plot_bounds.ytop  calculation */

    /*     first compute heights of things to be written in the margin */

    /* title */
    if (titlelin) {
	double tmpx, tmpy;
	map_position_r(&(title.offset), &tmpx, &tmpy, "title");
	if (title.font)
	    t->set_font(title.font);
	title_textheight = (int) ((titlelin) * (t->v_char) + tmpy);
	if (title.font)
	    t->set_font("");
	title_textheight += (int)(t->v_char); /* Gap of one normal line height */
    } else
	title_textheight = 0;

    /* x2label */
    if (x2lablin) {
	double tmpx, tmpy;
	map_position_r(&(axis_array[SECOND_X_AXIS].label.offset),
			&tmpx, &tmpy, "x2label");
	if (axis_array[SECOND_X_AXIS].label.font)
	    t->set_font(axis_array[SECOND_X_AXIS].label.font);
	x2label_textheight = (int) (x2lablin * t->v_char);
	x2label_yoffset = tmpy;
	if (axis_array[SECOND_X_AXIS].label.font)
	    t->set_font("");
    } else
	x2label_textheight = 0;

    /* tic labels */
    if (axis_array[SECOND_X_AXIS].ticmode & TICS_ON_BORDER) {
	/* ought to consider tics on axes if axis near border */
	x2tic_textheight = (int) (x2ticlin * t->v_char);
    } else
	x2tic_textheight = 0;

    /* tics */
    if (axis_array[SECOND_X_AXIS].ticmode & TICS_ON_BORDER) {
	x2tic_height = t->v_tic * axis_array[SECOND_X_AXIS].ticscale;
	if (axis_array[SECOND_X_AXIS].tic_in)
	    x2tic_height = -x2tic_height;
    } else
	x2tic_height = 0;

    /* Polar (theta) tic labels need space at top and bottom of plot */
    if (THETA_AXIS.ticmode) {
	/* FIXME:  Really 5% of polar grid radius, but we don't know that yet */
	ttic_textheight = 2. * t->v_char;
    }

    /* timestamp */
    if (timelabel.text) {
	int timelin;
	timelabel_textwidth = label_width(timelabel.text, &timelin);
	if (vertical_timelabel) {
	    timelabel_textheight = timelabel_textwidth * t->v_char;
	    timelabel_textwidth = (timelin + 1.5) * t->h_char;
	    timelabel.place.y = 0;
	} else {
	    timelabel_textheight = timelin * t->v_char;
	    timelabel_textwidth = timelabel_textwidth * t->h_char;
	    /* save textheight for use in do_key_bounds() */
	    timelabel.place.y = timelabel_textheight;
	}
    }

    /* ylabel placement */
    if (axis_array[FIRST_Y_AXIS].label.text) {
	if (can_rotate && axis_array[FIRST_Y_AXIS].label.rotate != 0) {
	    ylabel_textwidth = ylablin * t->v_char;
	    if (!axis_array[FIRST_Y_AXIS].ticmode)
		ylabel_textwidth += 0.5 * t->v_char;
	} else {
	    /* How on earth did this used to work when it was always set to 0? */
	    ylabel_textwidth = estimate_strlen(axis_array[FIRST_Y_AXIS].label.text) * t->h_char;
	}
    }

    /* y2label placement */
    if (axis_array[SECOND_Y_AXIS].label.text) {
	if (can_rotate && axis_array[SECOND_Y_AXIS].label.rotate != 0) {
	    y2label_textwidth = y2lablin * t->v_char;
	    if (!axis_array[SECOND_Y_AXIS].ticmode)
		y2label_textwidth += 0.5 * t->v_char;
	} else {
	    /* should be (estimate_strlen()*t->h_char) but that's not what 5.0 did */
	    /* this is an ugly ad hoc approximation of what it did before */
	    y2label_textwidth = estimate_strlen(axis_array[SECOND_Y_AXIS].label.text) * t->h_char;
	}
    }

    /* compute plot_bounds.ytop from the various components
     *     unless tmargin is explicitly specified
     */

    plot_bounds.ytop = (int) (0.5 + (ysize + yoffset) * (t->ymax-1));

    /* Sanity check top and bottom margins, in case the user got confused */
    if (bmargin.scalex == screen && tmargin.scalex == screen)
	if (bmargin.x > tmargin.x) {
	    double tmp = bmargin.x;
	    bmargin.x = tmargin.x;
	    tmargin.x = tmp;
	}

    if (tmargin.scalex == screen) {
	/* Specified as absolute position on the canvas */
	plot_bounds.ytop = (tmargin.x) * (float)(t->ymax-1);
    } else if (tmargin.x >=0) {
	/* Specified in terms of character height */
	plot_bounds.ytop -= (int)(tmargin.x * (float)t->v_char + 0.5);
    } else {
	/* Auto-calculation of space required */
	int top_margin = title_textheight;
	if (x2label_textheight + x2label_yoffset > 0)
	    top_margin += x2label_textheight;

	if (timelabel_textheight > top_margin && !timelabel_bottom && !vertical_timelabel)
	    top_margin = timelabel_textheight;

	top_margin += x2tic_textheight;
	top_margin += t->v_char;
	if (x2tic_height > 0)
	    top_margin += x2tic_height;
	top_margin += ttic_textheight;

	plot_bounds.ytop -= top_margin;
	if (plot_bounds.ytop == (int)(0.5 + (ysize + yoffset) * (t->ymax-1))) {
	    /* make room for the end of rotated ytics or y2tics */
	    plot_bounds.ytop -= (int) (t->h_char * 2);
	}
    }

    /*  end of preliminary plot_bounds.ytop calculation }}} */


    /*{{{  preliminary plot_bounds.xleft, needed for "under" */
    if (lmargin.scalex == screen)
	plot_bounds.xleft = lmargin.x * (float)t->xmax;
    else
	plot_bounds.xleft = xoffset * t->xmax
			  + t->h_char * (lmargin.x >= 0 ? lmargin.x : 1);
    /*}}} */


    /*{{{  tentative plot_bounds.xright, needed for "under" */
    if (rmargin.scalex == screen)
	plot_bounds.xright = rmargin.x * (float)(t->xmax - 1);
    else
	plot_bounds.xright = (xsize + xoffset) * (t->xmax - 1)
			   - t->h_char * (rmargin.x >= 0 ? rmargin.x : 2);
    /*}}} */


    /*{{{  preliminary plot_bounds.ybot calculation
     *     first compute heights of labels and tics */

    /* tic labels */
    shift_labels_to_border = FALSE;
    if (axis_array[FIRST_X_AXIS].ticmode & TICS_ON_AXIS) {
	/* FIXME: This test for how close the axis is to the border does not match */
	/*        the tests in axis_output_tics(), and assumes FIRST_Y_AXIS.       */
	if (!inrange(0.0, axis_array[FIRST_Y_AXIS].min, axis_array[FIRST_Y_AXIS].max))
	    shift_labels_to_border = TRUE;
	if (0.05 > fabs( axis_array[FIRST_Y_AXIS].min
		/ (axis_array[FIRST_Y_AXIS].max - axis_array[FIRST_Y_AXIS].min)))
	    shift_labels_to_border = TRUE;
    }
    if ((axis_array[FIRST_X_AXIS].ticmode & TICS_ON_BORDER)
    ||  shift_labels_to_border) {
	xtic_textheight = (int) (t->v_char * (xticlin + 1));
    } else
	xtic_textheight =  0;

    /* tics */
    if (!axis_array[FIRST_X_AXIS].tic_in
	&& ((axis_array[FIRST_X_AXIS].ticmode & TICS_ON_BORDER)
	    || ((axis_array[SECOND_X_AXIS].ticmode & TICS_MIRROR)
		&& (axis_array[SECOND_X_AXIS].ticmode & TICS_ON_BORDER))))
	xtic_height = (int) (t->v_tic * axis_array[FIRST_X_AXIS].ticscale);
    else
	xtic_height = 0;

    /* xlabel */
    if (xlablin) {
	double tmpx, tmpy;
	map_position_r(&(axis_array[FIRST_X_AXIS].label.offset),
		       &tmpx, &tmpy, "boundary");
	/* offset is subtracted because if > 0, the margin is smaller */
	/* textheight is inflated by 0.2 to allow descenders to clear bottom of canvas */
	xlabel_textheight = (((float)xlablin + 0.2) * t->v_char - tmpy);
	if (!axis_array[FIRST_X_AXIS].ticmode)
	    xlabel_textheight += 0.5 * t->v_char;
    } else
	xlabel_textheight = 0;

    /* compute plot_bounds.ybot from the various components
     *     unless bmargin is explicitly specified  */

    plot_bounds.ybot = yoffset * (float)t->ymax;

    if (bmargin.scalex == screen) {
	/* Absolute position for bottom of plot */
	plot_bounds.ybot = bmargin.x * (float)t->ymax;
    } else if (bmargin.x >= 0) {
	/* Position based on specified character height */
	plot_bounds.ybot += bmargin.x * (float)t->v_char + 0.5;
    } else {
	plot_bounds.ybot += xtic_height + xtic_textheight;
	if (xlabel_textheight > 0)
	    plot_bounds.ybot += xlabel_textheight;
	if (!vertical_timelabel && timelabel_bottom && timelabel_textheight > 0)
	    plot_bounds.ybot += timelabel_textheight;
	if (plot_bounds.ybot == (int) (t->ymax * yoffset)) {
	    /* make room for the end of rotated ytics or y2tics */
	    plot_bounds.ybot += (int) (t->h_char * 2);
	}
	/* Last chance for better estimate of space required for ttic labels */
	/* It is too late to go back and adjust positions relative to ytop */
	if (ttic_textheight > 0) {
	    ttic_textheight = 0.05 * (plot_bounds.ytop - plot_bounds.ybot);
	    plot_bounds.ybot += ttic_textheight;
	}
    }

    /*  end of preliminary plot_bounds.ybot calculation }}} */

    /* Determine the size and position of the key box */
    if (key->visible) {
	/* Count max_len key and number keys with len > 0 */
	max_ptitl_len = find_maxl_keys(plots, count, &ptitl_cnt);
	do_key_layout(key);
    }

    /* Adjust range of dependent axes y and y2 */
    if (nonlinear(&axis_array[FIRST_Y_AXIS]))
	extend_primary_ticrange(&axis_array[FIRST_Y_AXIS]);
    if (nonlinear(&axis_array[SECOND_Y_AXIS]))
	extend_primary_ticrange(&axis_array[SECOND_Y_AXIS]);
    setup_tics(&axis_array[FIRST_Y_AXIS], 20);
    setup_tics(&axis_array[SECOND_Y_AXIS], 20);

    /* Adjust color axis limits if necessary. */
    if (is_plot_with_palette()) {
	axis_checked_extend_empty_range(COLOR_AXIS, "All points of color axis undefined.");
	if (color_box.where != SMCOLOR_BOX_NO)
	    setup_tics(&axis_array[COLOR_AXIS], 20);
    }

    /*{{{  recompute plot_bounds.xleft based on widths of ytics, ylabel etc
       unless it has been explicitly set by lmargin */

    /* tic labels */
    shift_labels_to_border = FALSE;
    if (axis_array[FIRST_Y_AXIS].ticmode & TICS_ON_AXIS) {
	/* FIXME: This test for how close the axis is to the border does not match */
	/*        the tests in axis_output_tics(), and assumes FIRST_X_AXIS.       */
	if (!inrange(0.0, axis_array[FIRST_X_AXIS].min, axis_array[FIRST_X_AXIS].max))
	    shift_labels_to_border = TRUE;
	if (0.1 > fabs( axis_array[FIRST_X_AXIS].min
	       /  (axis_array[FIRST_X_AXIS].max - axis_array[FIRST_X_AXIS].min)))
	    shift_labels_to_border = TRUE;
    }

    if ((axis_array[FIRST_Y_AXIS].ticmode & TICS_ON_BORDER)
    ||  shift_labels_to_border) {
	if (vertical_ytics)
	    /* HBB: we will later add some white space as part of this, so
	     * reserve two more rows (one above, one below the text ...).
	     * Same will be done to similar calc.'s elsewhere */
	    ytic_textwidth = (int) (t->v_char * (yticlin + 2));
	else {
	    widest_tic_strlen = 0;	/* reset the global variable ... */
	    /* get gen_tics to call widest_tic_callback with all labels
	     * the latter sets widest_tic_strlen to the length of the widest
	     * one ought to consider tics on axis if axis near border...
	     */
	    gen_tics(&axis_array[FIRST_Y_AXIS], widest_tic_callback);

	    ytic_textwidth = (int) (t->h_char * (widest_tic_strlen + 2));
	}
    } else {
	ytic_textwidth = 0;
    }

    /* tics */
    if (!axis_array[FIRST_Y_AXIS].tic_in
	&& ((axis_array[FIRST_Y_AXIS].ticmode & TICS_ON_BORDER)
	    || ((axis_array[SECOND_Y_AXIS].ticmode & TICS_MIRROR)
		&& (axis_array[SECOND_Y_AXIS].ticmode & TICS_ON_BORDER))))
	ytic_width = (int) (t->h_tic * axis_array[FIRST_Y_AXIS].ticscale);
    else
	ytic_width = 0;

    if (lmargin.x < 0) {
	/* Auto-calculation */
	int space_to_left = key_xleft;

	if (space_to_left < timelabel_textwidth && vertical_timelabel)
	    space_to_left = timelabel_textwidth;
	if (space_to_left < ylabel_textwidth)
	    space_to_left = ylabel_textwidth;
	plot_bounds.xleft = xoffset * t->xmax;
	plot_bounds.xleft += space_to_left;
	plot_bounds.xleft += ytic_width + ytic_textwidth;

	if (plot_bounds.xleft - ytic_width - ytic_textwidth < 0)
	    plot_bounds.xleft = ytic_width + ytic_textwidth;
	if (plot_bounds.xleft == t->xmax * xoffset)
	    plot_bounds.xleft += t->h_char * 2;
	/* DBT 12-3-98  extra margin just in case */
	plot_bounds.xleft += 0.5 * t->h_char;
    }
    /* Note: we took care of explicit 'set lmargin foo' at line 492 */

    /*  end of plot_bounds.xleft calculation }}} */

    /*{{{  recompute plot_bounds.xright based on widest y2tic. y2labels, key "outside"
       unless it has been explicitly set by rmargin */

    /* tic labels */
    if (axis_array[SECOND_Y_AXIS].ticmode & TICS_ON_BORDER) {
	if (vertical_y2tics)
	    y2tic_textwidth = (int) (t->v_char * (y2ticlin + 2));
	else {
	    widest_tic_strlen = 0;	/* reset the global variable ... */
	    /* get gen_tics to call widest_tic_callback with all labels
	     * the latter sets widest_tic_strlen to the length of the widest
	     * one ought to consider tics on axis if axis near border...
	     */
	    gen_tics(&axis_array[SECOND_Y_AXIS], widest_tic_callback);

	    y2tic_textwidth = (int) (t->h_char * (widest_tic_strlen + 2));
	}
    } else {
	y2tic_textwidth = 0;
    }

    /* EAM May 2009
     * Check to see if any xtic labels are so long that they extend beyond
     * the right boundary of the plot. If so, allow extra room in the margin.
     * If the labels are too long to fit even with a big margin, too bad.
     */
    if (axis_array[FIRST_X_AXIS].ticdef.def.user) {
	struct ticmark *tic = axis_array[FIRST_X_AXIS].ticdef.def.user;
	int maxrightlabel = plot_bounds.xright;

	/* We don't really know the plot layout yet, but try for an estimate */
	axis_set_scale_and_range(&axis_array[FIRST_X_AXIS], plot_bounds.xleft, plot_bounds.xright);

	while (tic) {
	    if (tic->label) {
		double xx;
		int length = estimate_strlen(tic->label)
			   * cos(DEG2RAD * (double)(axis_array[FIRST_X_AXIS].tic_rotate))
			   * term->h_char;

		if (inrange(tic->position,
		    axis_array[FIRST_X_AXIS].set_min,
		    axis_array[FIRST_X_AXIS].set_max)) {
			xx = axis_log_value_checked(FIRST_X_AXIS, tic->position, "xtic");
			xx = map_x(xx);
			xx += (axis_array[FIRST_X_AXIS].tic_rotate) ? length : length /2;
			if (maxrightlabel < xx)
			    maxrightlabel = xx;
		}
	    }
	    tic = tic->next;
	}
	xtic_textwidth = maxrightlabel - plot_bounds.xright;
	if (xtic_textwidth > term->xmax/4) {
	    xtic_textwidth = term->xmax/4;
	    int_warn(NO_CARET, "difficulty making room for xtic labels");
	}
    }

    /* tics */
    if (!axis_array[SECOND_Y_AXIS].tic_in
	&& ((axis_array[SECOND_Y_AXIS].ticmode & TICS_ON_BORDER)
	    || ((axis_array[FIRST_Y_AXIS].ticmode & TICS_MIRROR)
		&& (axis_array[FIRST_Y_AXIS].ticmode & TICS_ON_BORDER))))
	y2tic_width = (int) (t->h_tic * axis_array[SECOND_Y_AXIS].ticscale);
    else
	y2tic_width = 0;

    /* Make room for the color box if needed. */
    if (rmargin.scalex != screen) {
	if (is_plot_with_colorbox()) {
#define COLORBOX_SCALE 0.100
#define WIDEST_COLORBOX_TICTEXT 3
	    if ((color_box.where != SMCOLOR_BOX_NO) && (color_box.where != SMCOLOR_BOX_USER)) {
		plot_bounds.xright -= (int) (plot_bounds.xright-plot_bounds.xleft)*COLORBOX_SCALE;
		plot_bounds.xright -= (int) ((t->h_char) * WIDEST_COLORBOX_TICTEXT);
	    }
	    color_box.xoffset = 0;
	}

	if (rmargin.x < 0) {
	    color_box.xoffset = plot_bounds.xright;
	    plot_bounds.xright -= y2tic_width + y2tic_textwidth;
	    if (y2label_textwidth > 0)
		plot_bounds.xright -= y2label_textwidth;

	    if (plot_bounds.xright > (xsize+xoffset)*(t->xmax-1) - (t->h_char * 2))
		plot_bounds.xright = (xsize+xoffset)*(t->xmax-1) - (t->h_char * 2);

	    color_box.xoffset -= plot_bounds.xright;
	    /* EAM 2009 - protruding xtic labels */
	    if (term->xmax - plot_bounds.xright < xtic_textwidth)
		plot_bounds.xright = term->xmax - xtic_textwidth;
	    /* DBT 12-3-98  extra margin just in case */
	    plot_bounds.xright -= 1.0 * t->h_char;
	}
	/* Note: we took care of explicit 'set rmargin foo' at line 502 */
    }

    /*  end of plot_bounds.xright calculation }}} */


    /* Set up x and x2 tics */
    /* we should base the guide on the width of the xtics, but we cannot
     * use widest_tics until tics are set up. Bit of a downer - let us
     * assume tics are 5 characters wide
     */
    /* HBB 20001205: moved this block to before aspect_ratio is
     * applied: setup_tics may extend the ranges, which would distort
     * the aspect ratio */

    setup_tics(&axis_array[FIRST_X_AXIS], 20);
    setup_tics(&axis_array[SECOND_X_AXIS], 20);

    /* Make sure that if polar grid is shown on a cartesian axis plot */
    /* the rtics match up with the primary x tics.                    */
    if (R_AXIS.ticmode && (polar || raxis)) {
	if (bad_axis_range(&R_AXIS) || (!polar && R_AXIS.min != 0)) {
	    set_explicit_range(&R_AXIS, 0.0, X_AXIS.max);
	    R_AXIS.min = 0;
	    R_AXIS.max = axis_array[FIRST_X_AXIS].max;
	    int_warn(NO_CARET, "resetting rrange");
	}
	setup_tics(&axis_array[POLAR_AXIS], 10);
    }


    /* Modify the bounding box to fit the aspect ratio, if any was
     * given. */
    if (aspect_ratio != 0.0) {
	double current_aspect_ratio;

	if (aspect_ratio < 0
	    && (X_AXIS.max - X_AXIS.min) != 0.0
	    ) {
	    current_aspect_ratio = - aspect_ratio
		* fabs((Y_AXIS.max - Y_AXIS.min) / (X_AXIS.max - X_AXIS.min));
	} else
	    current_aspect_ratio = aspect_ratio;

	/* Set aspect ratio if valid and sensible */
	/* EAM Mar 2008 - fixed borders take precedence over centering */
	if (current_aspect_ratio >= 0.01 && current_aspect_ratio <= 100.0) {
	    double current = ((double) (plot_bounds.ytop - plot_bounds.ybot))
			   / (plot_bounds.xright - plot_bounds.xleft);
	    double required = (current_aspect_ratio * t->v_tic) / t->h_tic;

	    if (current > required) {
		/* too tall */
		int old_height = plot_bounds.ytop - plot_bounds.ybot;
		int new_height = required * (plot_bounds.xright - plot_bounds.xleft);
		if (bmargin.scalex == screen)
		    plot_bounds.ytop = plot_bounds.ybot + new_height;
		else if (tmargin.scalex == screen)
		    plot_bounds.ybot = plot_bounds.ytop - new_height;
		else {
		    plot_bounds.ybot += (old_height - new_height) / 2;
		    plot_bounds.ytop -= (old_height - new_height) / 2;
		}

	    } else {
		int old_width = plot_bounds.xright - plot_bounds.xleft;
		int new_width = (plot_bounds.ytop - plot_bounds.ybot) / required;
		if (lmargin.scalex == screen)
		    plot_bounds.xright = plot_bounds.xleft + new_width;
		else if (rmargin.scalex == screen)
		    plot_bounds.xleft = plot_bounds.xright - new_width;
		else {
		    plot_bounds.xleft += (old_width - new_width) / 2;
		    plot_bounds.xright -= (old_width - new_width) / 2;
		}
	    }
	}
    }

    /*  Calculate space needed for tic label rotation.
     *  If [tb]margin is auto, move the plot boundary.
     *  Otherwise use textheight to adjust placement of various titles.
     */

    if (axis_array[SECOND_X_AXIS].ticmode & TICS_ON_BORDER && vertical_x2tics) {
	/* Assuming left justified tic labels. Correction below if they aren't */
	double projection = sin((double)axis_array[SECOND_X_AXIS].tic_rotate*DEG2RAD);
	if (axis_array[SECOND_X_AXIS].tic_pos == RIGHT)
	    projection *= -1;
	else if (axis_array[SECOND_X_AXIS].tic_pos == CENTRE)
	    projection = 0.5*fabs(projection);
	widest_tic_strlen = 0;		/* reset the global variable ... */
	gen_tics(&axis_array[SECOND_X_AXIS], widest_tic_callback);
	if (tmargin.x < 0) /* Undo original estimate */
	    plot_bounds.ytop += x2tic_textheight;
	/* Adjust spacing for rotation */
	if (projection > 0.0)
	    x2tic_textheight += (int) (t->h_char * (widest_tic_strlen)) * projection;
	if (tmargin.x < 0)
	    plot_bounds.ytop -= x2tic_textheight;
    }
    if (axis_array[FIRST_X_AXIS].ticmode & TICS_ON_BORDER && vertical_xtics) {
	double projection;
	/* This adjustment will happen again in axis_output_tics but we need it now */
	if (axis_array[FIRST_X_AXIS].tic_rotate == TEXT_VERTICAL
	&& !axis_array[FIRST_X_AXIS].manual_justify)
	    axis_array[FIRST_X_AXIS].tic_pos = RIGHT;
	if (axis_array[FIRST_X_AXIS].tic_rotate == 90)
	    projection = -1.0;
	else if (axis_array[FIRST_X_AXIS].tic_rotate == TEXT_VERTICAL)
	    projection = -1.0;
	else
	    projection = -sin((double)axis_array[FIRST_X_AXIS].tic_rotate*DEG2RAD);
	if (axis_array[FIRST_X_AXIS].tic_pos == RIGHT)
	    projection *= -1;
	widest_tic_strlen = 0;		/* reset the global variable ... */
	gen_tics(&axis_array[FIRST_X_AXIS], widest_tic_callback);

	if (bmargin.x < 0)
	    plot_bounds.ybot -= xtic_textheight;
	if (projection > 0.0)
	    xtic_textheight = (int) (t->h_char * widest_tic_strlen) * projection
			    + t->v_char;
	if (bmargin.x < 0)
	    plot_bounds.ybot += xtic_textheight;
    }

    /* EAM - FIXME
     * Notwithstanding all these fancy calculations, plot_bounds.ytop must always be above plot_bounds.ybot
     */
    if (plot_bounds.ytop < plot_bounds.ybot) {
	int i = plot_bounds.ytop;

	plot_bounds.ytop = plot_bounds.ybot;
	plot_bounds.ybot = i;
	FPRINTF((stderr,"boundary: Big problems! plot_bounds.ybot > plot_bounds.ytop\n"));
    }

    /*  compute coordinates for axis labels, title et al
     *     (some of these may not be used) */

    x2label_y = plot_bounds.ytop + x2label_textheight;
    x2label_y += 0.5 * t->v_char;
    if (x2label_textheight + x2label_yoffset >= 0) {
	x2label_y += 1.5 * x2tic_textheight;
	/* Adjust for the tics themselves */
	if (x2tic_height > 0)
	    x2label_y += x2tic_height;
    }

    title_x = (plot_bounds.xleft + plot_bounds.xright) / 2;
    title_y = plot_bounds.ytop + title_textheight + x2tic_textheight;
    title_y += ttic_textheight;
    if (x2label_y + x2label_yoffset > plot_bounds.ytop)
	title_y += x2label_textheight;
    if (x2tic_height > 0)
	title_y += x2tic_height;

    /* Shift upward by 0.2 line to allow for descenders in xlabel text */
    xlabel_y = plot_bounds.ybot - xtic_height - xtic_textheight - xlabel_textheight
	+ ((float)xlablin+0.2) * t->v_char;
    xlabel_y -= ttic_textheight;
    ylabel_x = plot_bounds.xleft - ytic_width - ytic_textwidth;
    ylabel_x -= ylabel_textwidth;

    y2label_x = plot_bounds.xright + y2tic_width + y2tic_textwidth;

    /* Nov 2016  - simplify placement of timestamp
     * Stamp the same place on the page regardless of plot margins
     */
    if (vertical_timelabel) {
	time_x = 1.5 * term->h_char;
	if (timelabel_bottom)
	    time_y = term->v_char;
	else
	    time_y = term->ymax - term->v_char;
    } else {
	time_x = 1.0 * term->h_char;
	if (timelabel_bottom)
	    time_y = timelabel_textheight - 0.5 * term->v_char;
	else
	    time_y = term->ymax;
    }

    xtic_y = plot_bounds.ybot - xtic_height
	- (int) (vertical_xtics ? t->h_char : t->v_char);

    x2tic_y = plot_bounds.ytop + (x2tic_height > 0 ? x2tic_height : 0)
	+ (vertical_x2tics ? (int) t->h_char : t->v_char);

    ytic_x = plot_bounds.xleft - ytic_width
	- (vertical_ytics
	   ? (ytic_textwidth - (int) t->v_char)
	   : (int) t->h_char);

    y2tic_x = plot_bounds.xright + y2tic_width
	+ (int) (vertical_y2tics ? t->v_char : t->h_char);

    /* restore text to horizontal [we tested rotation above] */
    (void) (*t->text_angle) (0);

    /* needed for map_position() below */
    axis_set_scale_and_range(&axis_array[FIRST_X_AXIS], plot_bounds.xleft, plot_bounds.xright);
    axis_set_scale_and_range(&axis_array[SECOND_X_AXIS], plot_bounds.xleft, plot_bounds.xright);
    axis_set_scale_and_range(&axis_array[FIRST_Y_AXIS], plot_bounds.ybot, plot_bounds.ytop);
    axis_set_scale_and_range(&axis_array[SECOND_Y_AXIS], plot_bounds.ybot, plot_bounds.ytop);

    /* Calculate limiting bounds of the key */
    do_key_bounds(key);


    /* Set default clipping to the plot boundary */
    clip_area = &plot_bounds;

    /* Sanity checks */
    if (plot_bounds.xright < plot_bounds.xleft
    ||  plot_bounds.ytop   < plot_bounds.ybot)
	int_warn(NO_CARET, "Terminal canvas area too small to hold plot."
			"\n\t    Check plot boundary and font sizes.");

}

/*}}} */

void
do_key_bounds(legend_key *key)
{
    struct termentry *t = term;

    key_height = key_title_height + key_title_extra
		+ key_rows * key_entry_height + key->height_fix * key_entry_height;
    key_width = key_col_wth * key_cols;

    /* Key inside plot boundaries */
    if (key->region == GPKEY_AUTO_INTERIOR_LRTBC
	|| (key->region == GPKEY_AUTO_EXTERIOR_LRTBC && key->vpos == JUST_CENTRE && key->hpos == CENTRE)) {
	if (key->vpos == JUST_TOP) {
	    key->bounds.ytop = plot_bounds.ytop - t->v_tic;
	    key->bounds.ybot = key->bounds.ytop - key_height;
	} else if (key->vpos == JUST_BOT) {
	    key->bounds.ybot = plot_bounds.ybot + t->v_tic;
	    key->bounds.ytop = key->bounds.ybot + key_height;
	} else /* (key->vpos == JUST_CENTRE) */ {
	    key->bounds.ybot = ((plot_bounds.ybot + plot_bounds.ytop) - key_height) / 2;
	    key->bounds.ytop = ((plot_bounds.ybot + plot_bounds.ytop) + key_height) / 2;
	}
	if (key->hpos == LEFT) {
	    key->bounds.xleft = plot_bounds.xleft + t->h_char;
	    key->bounds.xright = key->bounds.xleft + key_width;
	} else if (key->hpos == RIGHT) {
	    key->bounds.xright = plot_bounds.xright - t->h_char;
	    key->bounds.xleft = key->bounds.xright - key_width;
	} else /* (key->hpos == CENTER) */ {
	    key->bounds.xleft = ((plot_bounds.xright + plot_bounds.xleft) - key_width) / 2;
	    key->bounds.xright = ((plot_bounds.xright + plot_bounds.xleft) + key_width) / 2;
	}

    /* Key outside plot boundaries */
    } else if (key->region == GPKEY_AUTO_EXTERIOR_LRTBC || key->region == GPKEY_AUTO_EXTERIOR_MARGIN) {

	/* Vertical alignment */
	if (key->margin == GPKEY_TMARGIN) {
	    /* align top first since tmargin may be manual */
	    key->bounds.ytop = (ysize + yoffset) * t->ymax - t->v_tic;
	    key->bounds.ybot = key->bounds.ytop - key_height;
	} else if (key->margin == GPKEY_BMARGIN) {
	    /* align bottom first since bmargin may be manual */
	    key->bounds.ybot = yoffset * t->ymax + t->v_tic;
	    if (timelabel.rotate == 0 && timelabel_bottom && timelabel.place.y > 0)
		key->bounds.ybot += (int)(timelabel.place.y);
	    key->bounds.ytop = key->bounds.ybot + key_height;
	} else {
	    if (key->vpos == JUST_TOP) {
		/* align top first since tmargin may be manual */
		key->bounds.ytop = plot_bounds.ytop;
		key->bounds.ybot = key->bounds.ytop - key_height;
	    } else if (key->vpos == JUST_CENTRE) {
		key->bounds.ybot = ((plot_bounds.ybot + plot_bounds.ytop) - key_height) / 2;
		key->bounds.ytop = ((plot_bounds.ybot + plot_bounds.ytop) + key_height) / 2;
	    } else {
		/* align bottom first since bmargin may be manual */
		key->bounds.ybot = plot_bounds.ybot;
		key->bounds.ytop = key->bounds.ybot + key_height;
	    }
	}

	/* Horizontal alignment */
	if (key->margin == GPKEY_LMARGIN) {
	    /* align left first since lmargin may be manual */
	    key->bounds.xleft = xoffset * t->xmax + t->h_char;
	    key->bounds.xright = key->bounds.xleft + key_width;
	} else if (key->margin == GPKEY_RMARGIN) {
	    /* align right first since rmargin may be manual */
	    key->bounds.xright = (xsize + xoffset) * (t->xmax-1) - t->h_char;
	    key->bounds.xleft = key->bounds.xright - key_width;
	} else {
	    if (key->hpos == LEFT) {
		/* align left first since lmargin may be manual */
		key->bounds.xleft = plot_bounds.xleft;
		key->bounds.xright = key->bounds.xleft + key_width;
	    } else if (key->hpos == CENTRE) {
		key->bounds.xleft  = ((plot_bounds.xright + plot_bounds.xleft) - key_width) / 2;
		key->bounds.xright = ((plot_bounds.xright + plot_bounds.xleft) + key_width) / 2;
	    } else {
		/* align right first since rmargin may be manual */
		key->bounds.xright = plot_bounds.xright;
		key->bounds.xleft = key->bounds.xright - key_width;
	    }
	}

    /* Key at explicit position specified by user */
    } else {
	int x, y;

	/* FIXME!!!
	 * pm 22.1.2002: if key->user_pos.scalex or scaley == first_axes or second_axes,
	 * then the graph scaling is not yet known and the box is positioned incorrectly;
	 * you must do "replot" to avoid the wrong plot ... bad luck if output does not
	 * go to screen
	 */
	map_position(&key->user_pos, &x, &y, "key");

	/* Here top, bottom, left, right refer to the alignment with respect to point. */
	key->bounds.xleft = x;
	if (key->hpos == CENTRE)
	    key->bounds.xleft -= key_width/2;
	else if (key->hpos == RIGHT)
	    key->bounds.xleft -= key_width;
	key->bounds.xright = key->bounds.xleft + key_width;
	key->bounds.ytop = y;
	if (key->vpos == JUST_CENTRE)
	    key->bounds.ytop += key_height/2;
	else if (key->vpos == JUST_BOT)
	    key->bounds.ytop += key_height;
	key->bounds.ybot = key->bounds.ytop - key_height;
    }
}

/* Calculate positioning of components that make up the key box */
void
do_key_layout(legend_key *key)
{
    struct termentry *t = term;
    TBOOLEAN key_panic = FALSE;

    /* If there is a separate font for the key, use it for space calculations.	*/
    if (key->font)
	t->set_font(key->font);

    key_xleft = 0;

    if (key->swidth >= 0) {
	key_sample_width = key->swidth * t->h_char + t->h_tic;
    } else {
	key_sample_width = 0;
    }

    key_sample_height = GPMAX( 1.25 * t->v_tic, t->v_char );
    key_entry_height = key_sample_height * key->vert_factor;
    /* HBB 20020122: safeguard to prevent division by zero later */
    if (key_entry_height == 0)
	key_entry_height = 1;

    /* Key title length and height */
    key_title_height = 0;
    key_title_extra = 0;
    if (key->title.text) {
	int ytheight;
	(void) label_width(key->title.text, &ytheight);
	key_title_height = ytheight * t->v_char;
	if ((*key->title.text) && (t->flags & TERM_ENHANCED_TEXT)
	&&  (strchr(key->title.text,'^') || strchr(key->title.text,'_')))
	    key_title_extra = t->v_char;
    }

    if (key->reverse) {
	key_sample_left = -key_sample_width;
	key_sample_right = 0;
	/* if key width is being used, adjust right-justified text */
	key_text_left = t->h_char;
	key_text_right = t->h_char * (max_ptitl_len + 1 + key->width_fix);
	key_size_left = t->h_char - key_sample_left; /* sample left is -ve */
	key_size_right = key_text_right;
    } else {
	key_sample_left = 0;
	key_sample_right = key_sample_width;
	/* if key width is being used, adjust left-justified text */
	key_text_left = -(int) (t->h_char
				* (max_ptitl_len + 1 + key->width_fix));
	key_text_right = -(int) t->h_char;
	key_size_left = -key_text_left;
	key_size_right = key_sample_right + t->h_char;
    }
    key_point_offset = (key_sample_left + key_sample_right) / 2;

    /* advance width for cols */
    key_col_wth = key_size_left + key_size_right;

    key_rows = ptitl_cnt;
    key_cols = 1;

    /* calculate rows and cols for key */

    if (key->stack_dir == GPKEY_HORIZONTAL) {
	/* maximise no cols, limited by label-length */
	key_cols = (int) (plot_bounds.xright - plot_bounds.xleft) / key_col_wth;
	if (key->maxcols > 0 && key_cols > key->maxcols)
	    key_cols = key->maxcols;
	/* EAM Dec 2004 - Rather than turn off the key, try to squeeze */
	if (key_cols == 0) {
	    key_cols = 1;
	    key_panic = TRUE;
	    key_col_wth = (plot_bounds.xright - plot_bounds.xleft);
	}
	key_rows = (ptitl_cnt + key_cols - 1) / key_cols;
	/* now calculate actual no cols depending on no rows */
	key_cols = (key_rows == 0) ? 1 : (ptitl_cnt + key_rows - 1) / key_rows;
	if (key_cols == 0) {
	    key_cols = 1;
	}
    } else {
	/* maximise no rows, limited by plot_bounds.ytop-plot_bounds.ybot */
	int i = (plot_bounds.ytop - plot_bounds.ybot - key->height_fix * key_entry_height
		    - key_title_height - key_title_extra)
		/ key_entry_height;
	if (key->maxrows > 0 && i > key->maxrows)
	    i = key->maxrows;

	if (i == 0) {
	    i = 1;
	    key_panic = TRUE;
	}
	if (ptitl_cnt > i) {
	    key_cols = (ptitl_cnt + i - 1) / i;
	    /* now calculate actual no rows depending on no cols */
	    if (key_cols == 0) {
		key_cols = 1;
		key_panic = TRUE;
	    }
	    key_rows = (ptitl_cnt + key_cols - 1) / key_cols;
	}
    }

    /* If the key title is wider than the contents, try to make room for it */
    if (key->title.text) {
	int ytlen = label_width(key->title.text, NULL) - key->swidth + 2;
	ytlen *= t->h_char;
	if (ytlen > key_cols * key_col_wth)
	    key_col_wth = ytlen / key_cols;
    }

    /* Adjust for outside key, leave manually set margins alone */
    if ((key->region == GPKEY_AUTO_EXTERIOR_LRTBC && (key->vpos != JUST_CENTRE || key->hpos != CENTRE))
	|| key->region == GPKEY_AUTO_EXTERIOR_MARGIN) {
	int more = 0;
	if (key->margin == GPKEY_BMARGIN && bmargin.x < 0) {
	    more = key_rows * key_entry_height + key_title_height + key_title_extra
		    + key->height_fix * key_entry_height;
	    if (plot_bounds.ybot + more > plot_bounds.ytop)
		key_panic = TRUE;
	    else
		plot_bounds.ybot += more;
	} else if (key->margin == GPKEY_TMARGIN && tmargin.x < 0) {
	    more = key_rows * key_entry_height + key_title_height + key_title_extra
		    + key->height_fix * key_entry_height;
	    if (plot_bounds.ytop - more < plot_bounds.ybot)
		key_panic = TRUE;
	    else
		plot_bounds.ytop -= more;
	} else if (key->margin == GPKEY_LMARGIN && lmargin.x < 0) {
	    more = key_col_wth * key_cols;
	    if (plot_bounds.xleft + more > plot_bounds.xright)
		key_panic = TRUE;
	    else
		key_xleft = more;
	    plot_bounds.xleft += key_xleft;
	} else if (key->margin == GPKEY_RMARGIN && rmargin.x < 0) {
	    more = key_col_wth * key_cols;
	    if (plot_bounds.xright - more < plot_bounds.xleft)
		key_panic = TRUE;
	    else
		plot_bounds.xright -= more;
	}
    }

    /* Restore default font */
    if (key->font)
	t->set_font("");

    /* warn if we had to punt on key size calculations */
    if (key_panic)
	int_warn(NO_CARET, "Warning - difficulty fitting plot titles into key");
}

int
find_maxl_keys(struct curve_points *plots, int count, int *kcnt)
{
    int mlen, len, curve, cnt;
    int previous_plot_style = 0;
    struct curve_points *this_plot;

    mlen = cnt = 0;
    this_plot = plots;
    for (curve = 0; curve < count; this_plot = this_plot->next, curve++) {
	if (this_plot->title && !this_plot->title_is_suppressed && !this_plot->title_position) {
	    ignore_enhanced(this_plot->title_no_enhanced);
	    len = estimate_strlen(this_plot->title);
	    if (len != 0) {
		cnt++;
		if (len > mlen)
		    mlen = len;
	    }
	    ignore_enhanced(FALSE);
	}

	/* Check for new histogram here and save space for divider */
	if (this_plot->plot_style == HISTOGRAMS
	&&  previous_plot_style == HISTOGRAMS
	&&  this_plot->histogram_sequence == 0 && cnt > 1)
	    cnt++;
	/* Check for column-stacked histogram with key entries */
	if (this_plot->plot_style == HISTOGRAMS &&  this_plot->labels) {
	    text_label *key_entry = this_plot->labels->next;
	    for (; key_entry; key_entry=key_entry->next) {
		cnt++;
		len = key_entry->text ? estimate_strlen(key_entry->text) : 0;
		if (len > mlen)
		    mlen = len;
	    }
	}
	previous_plot_style = this_plot->plot_style;
    }

    if (kcnt != NULL)
	*kcnt = cnt;
    return (mlen);
}

/*
 * Make the key sample code a subroutine so that it can eventually be
 * shared by the 3d code also. As of now the two code sections are not
 * very parallel.  EAM Nov 2003
 */

void
do_key_sample(
    struct curve_points *this_plot,
    legend_key *key,
    char *title,
    int xl, int yl)
{
    struct termentry *t = term;

    /* Clip key box against canvas */
    BoundingBox *clip_save = clip_area;
    if (term->flags & TERM_CAN_CLIP)
	clip_area = NULL;
    else
	clip_area = &canvas;

    /* If the plot this title belongs to specified a non-standard place */
    /* for the key sample to appear, use that to override xl, yl.       */
    if (this_plot->title_position && this_plot->title_position->scalex != character) {
	map_position(this_plot->title_position, &xl, &yl, "key sample");
	xl -=  (key->just == GPKEY_LEFT) ? key_text_left : key_text_right;
    }

    (*t->layer)(TERM_LAYER_BEGIN_KEYSAMPLE);

    if (key->textcolor.type == TC_VARIABLE)
	/* Draw key text in same color as plot */
	;
    else if (key->textcolor.type != TC_DEFAULT)
	/* Draw key text in same color as key title */
	apply_pm3dcolor(&key->textcolor);
    else
	/* Draw key text in black */
	(*t->linetype)(LT_BLACK);

    if (key->just == GPKEY_LEFT) {
	write_multiline(xl + key_text_left, yl, title, LEFT, JUST_CENTRE, 0, key->font);
    } else {
	if ((*t->justify_text) (RIGHT)) {
	    write_multiline(xl + key_text_right, yl, title, RIGHT, JUST_CENTRE, 0, key->font);
	} else {
	    int x = xl + key_text_right - t->h_char * estimate_strlen(title);
	    if (key->region == GPKEY_AUTO_EXTERIOR_LRTBC ||	/* HBB 990327 */
		key->region == GPKEY_AUTO_EXTERIOR_MARGIN ||
		inrange((x), (plot_bounds.xleft), (plot_bounds.xright)))
		write_multiline(x, yl, title, LEFT, JUST_CENTRE, 0, key->font);
	}
    }

    /* Draw sample in same style and color as the corresponding plot  */
    /* The variable color case uses the color of the first data point */
    if (!check_for_variable_color(this_plot, &this_plot->varcolor[0]))
	term_apply_lp_properties(&this_plot->lp_properties);

    /* draw sample depending on bits set in plot_style */
    if (this_plot->plot_style & PLOT_STYLE_HAS_FILL && t->fillbox) {
	struct fill_style_type *fs = &this_plot->fill_properties;
	int style = style_from_fill(fs);
	unsigned int x = xl + key_sample_left;
	unsigned int y = yl - key_sample_height/4;
	unsigned int w = key_sample_right - key_sample_left;
	unsigned int h = key_sample_height/2;

#ifdef EAM_OBJECTS
	if (this_plot->plot_style == CIRCLES && w > 0) {
	    do_arc(xl + key_point_offset, yl, key_sample_height/4, 0., 360., style, FALSE);
	    /* Retrace the border if the style requests it */
	    if (need_fill_border(fs)) {
	        do_arc(xl + key_point_offset, yl, key_sample_height/4, 0., 360., 0, FALSE);
	    }
	} else if (this_plot->plot_style == ELLIPSES && w > 0) {
	    t_ellipse *key_ellipse = (t_ellipse *) gp_alloc(sizeof(t_ellipse),
	        "cute little ellipse for the key sample");
	    key_ellipse->center.x = xl + key_point_offset;
	    key_ellipse->center.y = yl;
	    key_ellipse->extent.x = w * 2/3;
	    key_ellipse->extent.y = h;
	    key_ellipse->orientation = 0.0;
	    /* already in term coords, no need to map */
	    do_ellipse(2, key_ellipse, style, FALSE);
	    /* Retrace the border if the style requests it */
	    if (need_fill_border(fs)) {
		do_ellipse(2, key_ellipse, 0, FALSE);
	    }
	    free(key_ellipse);
	} else
#endif
	if (w > 0) {    /* All other plot types with fill */
	    if (style != FS_EMPTY)
		(*t->fillbox)(style,x,y,w,h);

	    /* need_fill_border will set the border linetype, but candlesticks don't want it */
	    if ((this_plot->plot_style == CANDLESTICKS && fs->border_color.type == TC_LT
							&& fs->border_color.lt == LT_NODRAW)
	    ||   style == FS_EMPTY
	    ||   need_fill_border(fs)) {
		newpath();
		draw_clip_line( xl + key_sample_left,  yl - key_sample_height/4,
				xl + key_sample_right, yl - key_sample_height/4);
		draw_clip_line( xl + key_sample_right, yl - key_sample_height/4,
				xl + key_sample_right, yl + key_sample_height/4);
		draw_clip_line( xl + key_sample_right, yl + key_sample_height/4,
				xl + key_sample_left,  yl + key_sample_height/4);
		draw_clip_line( xl + key_sample_left,  yl + key_sample_height/4,
				xl + key_sample_left,  yl - key_sample_height/4);
		closepath();
	    }
	    if (fs->fillstyle != FS_EMPTY && fs->fillstyle != FS_DEFAULT
	    && !(fs->border_color.type == TC_LT && fs->border_color.lt == LT_NODRAW)) {
		/* need_fill_border() might have changed our original linetype */
		term_apply_lp_properties(&this_plot->lp_properties);
	    }
	}

    } else if (this_plot->plot_style == VECTOR && t->arrow) {
	apply_head_properties(&(this_plot->arrow_properties));
	draw_clip_arrow(xl + key_sample_left, yl, xl + key_sample_right, yl,
		    this_plot->arrow_properties.head);

    } else if (this_plot->lp_properties.l_type == LT_NODRAW) {
	;

    } else if ((this_plot->plot_style & PLOT_STYLE_HAS_ERRORBAR) &&  this_plot->plot_type == DATA) {
	/* errors for data plots only */
	if ((bar_lp.flags & LP_ERRORBAR_SET) != 0)
	    term_apply_lp_properties(&bar_lp);
	draw_clip_line(xl + key_sample_left, yl, xl + key_sample_right, yl);
	/* Even if error bars are dotted, the end lines are always solid */
	if ((bar_lp.flags & LP_ERRORBAR_SET) != 0)
	    term->dashtype(DASHTYPE_SOLID,NULL);

    } else if ((this_plot->plot_style & PLOT_STYLE_HAS_LINE)) {
	draw_clip_line(xl + key_sample_left, yl, xl + key_sample_right, yl);
    }

    if ((this_plot->plot_type == DATA)
	&& (this_plot->plot_style & PLOT_STYLE_HAS_ERRORBAR)
	&& (this_plot->plot_style != CANDLESTICKS)
	&& (bar_size > 0.0)) {
	draw_clip_line( xl + key_sample_left, yl + ERRORBARTIC,
			xl + key_sample_left, yl - ERRORBARTIC);
	draw_clip_line( xl + key_sample_right, yl + ERRORBARTIC,
			xl + key_sample_right, yl - ERRORBARTIC);
    }

    /* oops - doing the point sample now would break the postscript
     * terminal for example, which changes current line style
     * when drawing a point, but does not restore it. We must wait to
     * draw the point sample at the end of do_plot (comment KEY SAMPLES).
     */

    (*t->layer)(TERM_LAYER_END_KEYSAMPLE);

    /* Restore previous clipping area */
    clip_area = clip_save;
}

void
do_key_sample_point(
    struct curve_points *this_plot,
    legend_key *key,
    int xl, int yl)
{
    struct termentry *t = term;

    /* If the plot this title belongs to specified a non-standard place */
    /* for the key sample to appear, use that to override xl, yl.       */
    if (this_plot->title_position && this_plot->title_position->scalex != character) {
	map_position(this_plot->title_position, &xl, &yl, "key sample");
	xl -=  (key->just == GPKEY_LEFT) ? key_text_left : key_text_right;
    }

    (t->layer)(TERM_LAYER_BEGIN_KEYSAMPLE);

    if (this_plot->plot_style == LINESPOINTS
	 &&  this_plot->lp_properties.p_interval < 0) {
	t_colorspec background_fill = BACKGROUND_COLORSPEC;
	(*t->set_color)(&background_fill);
	(*t->pointsize)(pointsize * pointintervalbox);
	(*t->point)(xl + key_point_offset, yl, 6);
	term_apply_lp_properties(&this_plot->lp_properties);
    }

    if (this_plot->plot_style == BOXPLOT) {
	;	/* Don't draw a sample point in the key */

    } else if (this_plot->plot_style == DOTS) {
	if (on_page(xl + key_point_offset, yl))
	    (*t->point) (xl + key_point_offset, yl, -1);

    } else if (this_plot->plot_style & PLOT_STYLE_HAS_POINT) {
	if (this_plot->lp_properties.p_size == PTSZ_VARIABLE)
	    (*t->pointsize)(pointsize);
	if (on_page(xl + key_point_offset, yl)) {
	    if (this_plot->lp_properties.p_type == PT_CHARACTER) {
		apply_pm3dcolor(&(this_plot->labels->textcolor));
		(*t->put_text) (xl + key_point_offset, yl, 
				this_plot->lp_properties.p_char);
		apply_pm3dcolor(&(this_plot->lp_properties.pm3d_color));
	    } else {
		(*t->point) (xl + key_point_offset, yl, 
				this_plot->lp_properties.p_type);
	    }
	}

    } else if (this_plot->plot_style == LABELPOINTS) {
	struct text_label *label = this_plot->labels;
	if (label->lp_properties.flags & LP_SHOW_POINTS) {
	    term_apply_lp_properties(&label->lp_properties);
	    (*t->point) (xl + key_point_offset, yl, label->lp_properties.p_type);
	}
    }

    (t->layer)(TERM_LAYER_END_KEYSAMPLE);
}

/* Graph legend is now optionally done in two passes. The first pass calculates	*/
/* and reserves the necessary space.  Next the individual plots in the graph 	*/
/* are drawn. Then the reserved space for the legend is blanked out, and 	*/
/* finally the second pass through this code draws the legend.			*/
void
draw_key(legend_key *key, TBOOLEAN key_pass, int *xinkey, int *yinkey)
{
    struct termentry *t = term;

    /* In two-pass mode (set key opaque) we blank out the key box after	*/
    /* the graph is drawn and then redo the key in the blank area.	*/
    if (key_pass && t->fillbox && !(t->flags & TERM_NULL_SET_COLOR)) {
	t_colorspec background_fill = BACKGROUND_COLORSPEC;
	(*t->set_color)(&background_fill);
	(*t->fillbox)(FS_OPAQUE, key->bounds.xleft, key->bounds.ybot,
		key_width, key_height);
    }

    if (key->title.text) {
	int title_anchor;
	if (key->title.pos == CENTRE)
		title_anchor = (key->bounds.xleft + key->bounds.xright) / 2;
	else if (key->title.pos == RIGHT)
		title_anchor = key->bounds.xright - term->h_char;
	else
		title_anchor = key->bounds.xleft + term->h_char;

	/* Only draw the title once */
	if (key_pass || !key->front) {
	    write_label(title_anchor,
			key->bounds.ytop - (key_title_extra + key_entry_height)/2,
			&key->title);
	    (*t->linetype)(LT_BLACK);
	}
    }

    if (key->box.l_type > LT_NODRAW) {
	BoundingBox *clip_save = clip_area;
	if (term->flags & TERM_CAN_CLIP)
	    clip_area = NULL;
	else
	    clip_area = &canvas;
	term_apply_lp_properties(&key->box);
	newpath();
	draw_clip_line(key->bounds.xleft, key->bounds.ybot, key->bounds.xleft, key->bounds.ytop);
	draw_clip_line(key->bounds.xleft, key->bounds.ytop, key->bounds.xright, key->bounds.ytop);
	draw_clip_line(key->bounds.xright, key->bounds.ytop, key->bounds.xright, key->bounds.ybot);
	draw_clip_line(key->bounds.xright, key->bounds.ybot, key->bounds.xleft, key->bounds.ybot);
	closepath();
	/* draw a horizontal line between key title and first entry */
	if (key->title.text)
	    draw_clip_line( key->bounds.xleft,
	    		    key->bounds.ytop - (key_title_height + key_title_extra),
			    key->bounds.xright,
	    		    key->bounds.ytop - (key_title_height + key_title_extra));
	clip_area = clip_save;
    }

    yl_ref = key->bounds.ytop - (key_title_height + key_title_extra);
    yl_ref -= ((key->height_fix + 1) * key_entry_height) / 2;
    *xinkey = key->bounds.xleft + key_size_left;
    *yinkey = yl_ref;
}

/*
 * This routine draws the plot title, the axis labels, and an optional time stamp.
 */
void
draw_titles()
{
    struct termentry *t = term;

    /* YLABEL */
    if (axis_array[FIRST_Y_AXIS].label.text) {
	unsigned int x = ylabel_x;
	unsigned int y = (plot_bounds.ytop + plot_bounds.ybot) / 2;
	x += t->h_char;
	axis_array[FIRST_Y_AXIS].label.pos = CENTRE;
	write_label(x, y, &(axis_array[FIRST_Y_AXIS].label));
	reset_textcolor(&(axis_array[FIRST_Y_AXIS].label.textcolor));
    }

    /* Y2LABEL */
    if (axis_array[SECOND_Y_AXIS].label.text) {
	unsigned int x = y2label_x;
	unsigned int y = (plot_bounds.ytop + plot_bounds.ybot) / 2;
	if (axis_array[SECOND_Y_AXIS].label.rotate) {
	    x += 2 * t->h_char;
	    axis_array[SECOND_Y_AXIS].label.pos = CENTRE;
	} else {
	    axis_array[SECOND_Y_AXIS].label.pos = LEFT;
	}
	write_label(x, y, &(axis_array[SECOND_Y_AXIS].label));
	reset_textcolor(&(axis_array[SECOND_Y_AXIS].label.textcolor));
    }

    /* XLABEL */
    if (axis_array[FIRST_X_AXIS].label.text) {
	struct text_label *label = &axis_array[FIRST_X_AXIS].label;
	double tmpx, tmpy;
	unsigned int x, y;
	map_position_r(&(label->offset), &tmpx, &tmpy, "xlabel");

	x = (plot_bounds.xright + plot_bounds.xleft) / 2;
	y = xlabel_y - t->v_char / 2;
	y -= tmpy;	/* xlabel_y already contained tmpy */

	write_label(x, y, label);
	reset_textcolor(&(label->textcolor));
    }

    /* PLACE TITLE */
    if (title.text) {
	double tmpx, tmpy;
	unsigned int x, y;
	map_position_r(&(title.offset), &tmpx, &tmpy, "doplot");
	/* we worked out y-coordinate in boundary(), including the y offset */
	x = title_x;
	y = title_y - tmpy - t->v_char / 2;

	/* NB: write_label applies text color but does not reset it */
	write_label(x, y, &title);
	reset_textcolor(&(title.textcolor));
    }

    /* X2LABEL */
    if (axis_array[SECOND_X_AXIS].label.text) {
	unsigned int x, y;
	/* we worked out y-coordinate in boundary() */
	x = (plot_bounds.xright + plot_bounds.xleft) / 2;
	y = x2label_y - t->v_char / 2;
	write_label(x, y, &(axis_array[SECOND_X_AXIS].label));
	reset_textcolor(&(axis_array[SECOND_X_AXIS].label.textcolor));
    }

    /* RLABEL */
    if (axis_array[POLAR_AXIS].label.text) {
	unsigned int x, y;

	/* This assumes we always have a horizontal R axis */
	x = map_x(polar_radius(R_AXIS.max) / 2.0);
	y = map_y(0.0) + t->v_char;
	write_label(x, y, &(axis_array[POLAR_AXIS].label));
	reset_textcolor(&(axis_array[POLAR_AXIS].label.textcolor));
    }

    /* PLACE TIMELABEL */
    if (timelabel.text)
	do_timelabel(time_x, time_y);
}