//      Routines for drawing axes & box around the current viewport.
//
// Copyright (C) 2004  Joao Cardoso
// Copyright (C) 2004-2014 Alan W. Irwin
//
// This file is part of PLplot.
//
// PLplot is free software; you can redistribute it and/or modify
// it under the terms of the GNU Library General Public License as published
// by the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// PLplot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public License
// along with PLplot; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//

#include "plplotP.h"

#define STRING_LEN         40
#define FORMAT_LEN         10
#define TEMP_LEN           30
#define N_EDGE_SEGMENTS    50

static PLFLT xlog[8] =
{
    0.301030, 0.477121, 0.602060, 0.698970,
    0.778151, 0.845098, 0.903090, 0.954243
};

// Static function prototypes

static void
plxybx( const char *opt, const char *label, PLINT axis, PLFLT wx1, PLFLT wy1,
        PLFLT wx2, PLFLT wy2, PLFLT vmin, PLFLT vmax,
        PLFLT tick, PLINT nsub, PLINT nolast, PLINT *digits );

static void
plzbx( const char *opt, const char *label, PLINT right, PLFLT dx, PLFLT dy,
       PLFLT wx, PLFLT wy1, PLFLT wy2, PLFLT vmin, PLFLT vmax,
       PLFLT tick, PLINT nsub, PLINT *digits );

static void
plxytx( PLFLT wx1, PLFLT wy1, PLFLT wx2, PLFLT wy2,
        PLFLT disp, PLFLT pos, PLFLT just, const char *text );

static void
plztx( const char *opt, PLFLT dx, PLFLT dy, PLFLT wx, PLFLT wy1,
       PLFLT wy2, PLFLT disp, PLFLT pos, PLFLT just, const char *text );

static void
plform( PLINT axis, PLFLT value, PLINT scale, PLINT prec, char *result, PLINT len, PLBOOL ll, PLBOOL lf, PLBOOL lo );

static void
grid_box( const char *xopt, PLFLT xtick1, PLINT nxsub1,
          const char *yopt, PLFLT ytick1, PLINT nysub1 );

static void
label_box( const char *xopt, PLFLT xtick1, const char *yopt, PLFLT ytick1 );

static void
plP_default_label_log( PLINT axis, PLFLT value, char *string, PLINT len, void *data );

static void
plP_default_label_log_fixed( PLINT axis, PLFLT value, char *string, PLINT len, void *data );

static void
plP_default_label( PLINT axis, PLFLT value, char *string, PLINT len, void *data );

static const char *
plgesc_string( void );

//--------------------------------------------------------------------------
// void plbox()
//
// This draws a box around the current viewport, complete with axes, ticks,
// numeric labels, and grids, according to input specification.  Just a
// front-end to plaxes(), which allows arbitrary placement of coordinate
// axes when plotted (here the origin is at 0,0).  See the documentation for
// plaxes() for more info.
//--------------------------------------------------------------------------

void
c_plbox( const char *xopt, PLFLT xtick, PLINT nxsub,
         const char *yopt, PLFLT ytick, PLINT nysub )
{
    c_plaxes( 0.0, 0.0, xopt, xtick, nxsub, yopt, ytick, nysub );
}

//--------------------------------------------------------------------------
// void plaxes()
//
// This draws a box around the current viewport, complete with axes,
// ticks, numeric labels, and grids, according to input specification.
//
// x0 and y0 specify the origin of the axes.
//
// xopt and yopt are character strings which define the box as follows:
//
// a: Draw axis (X is horizontal line Y=0, Y is vertical line X=0)
// b: Draw bottom (X) or left (Y) frame of box
// c: Draw top (X) or right (Y) frame of box
// d: Interpret axis as a date/time when writing labels
// f: Always use fixed point numeric labels
// g: Draws a grid at the major tick interval
// h: Draws a grid at the minor tick interval
// i: Inverts tick marks
// l: Logarithmic axes, major ticks at decades, minor ticks at units
// n: Write numeric label at conventional location
// m: Write numeric label at unconventional location
// o: Label text is generated by a user-defined function
// t: Draw major tick marks
// s: Draw minor tick marks
// u: like b (including all side effects such as tick marks and numerical
// labels for those) except exclude drawing the edge.
// w: like c (including all side effects such as tick marks and numerical
// labels for those) except exclude drawing the edge.
// v: (for Y only) Label vertically
// x: like t (including the side effect of the numerical labels for the major
// ticks) except exclude drawing the major and minor tick marks.
//
// xtick, ytick are the major tick intervals required, zero for
// automatic selection
//
// nxsub, nysub are the number of subtick intervals in a major tick
// interval
//--------------------------------------------------------------------------

void
c_plaxes( PLFLT x0, PLFLT y0,
          const char *xopt, PLFLT xtick, PLINT nxsub,
          const char *yopt, PLFLT ytick, PLINT nysub )
{
    PLBOOL lax, lbx, lcx, ldx, lgx, lix, llx, lsx, ltx, lux, lwx, lxx;
    PLBOOL lay, lby, lcy, ldy, lgy, liy, lly, lsy, lty, luy, lwy, lxy;
    PLINT  xmajor, xminor, ymajor, yminor;
    PLINT  i, i1x, i2x, i3x, i4x, i1y, i2y, i3y, i4y;
    PLINT  nxsub1, nysub1;
    PLINT  lxmin, lxmax, lymin, lymax;
    PLINT  pxmin, pxmax, pymin, pymax;
    PLINT  vppxmi, vppxma, vppymi, vppyma;
    PLFLT  xtick1, ytick1, vpwxmi, vpwxma, vpwymi, vpwyma;
    PLFLT  vpwxmin, vpwxmax, vpwymin, vpwymax;
    PLFLT  xp0, yp0, tn, tp, temp;
    PLFLT  factor, tstart;

    if ( plsc->level < 3 )
    {
        plabort( "plbox: Please set up window first" );
        return;
    }

// Open the clip limits to the subpage limits

    plP_gclp( &lxmin, &lxmax, &lymin, &lymax );
    plP_gphy( &pxmin, &pxmax, &pymin, &pymax );
    plP_sclp( pxmin, pxmax, pymin, pymax );

    vppxmi = plsc->vppxmi;
    vppxma = plsc->vppxma;
    vppymi = plsc->vppymi;
    vppyma = plsc->vppyma;

    if ( plsc->if_boxbb )
    {
        // Bounding-box limits for the box in mm before corrections
        // for decorations are applied.
        plsc->boxbb_xmin = plsc->vppxmi / plsc->xpmm;
        plsc->boxbb_xmax = plsc->vppxma / plsc->xpmm;
        plsc->boxbb_ymin = plsc->vppymi / plsc->ypmm;
        plsc->boxbb_ymax = plsc->vppyma / plsc->ypmm;
    }

// Set plot options from input

    lax = plP_stsearch( xopt, 'a' );
    lbx = plP_stsearch( xopt, 'b' );
    lcx = plP_stsearch( xopt, 'c' );
    ldx = plP_stsearch( xopt, 'd' );
    lgx = plP_stsearch( xopt, 'g' );
    lix = plP_stsearch( xopt, 'i' );
    llx = plP_stsearch( xopt, 'l' );
    lsx = plP_stsearch( xopt, 's' );
    ltx = plP_stsearch( xopt, 't' );
    lux = plP_stsearch( xopt, 'u' );
    lwx = plP_stsearch( xopt, 'w' );
    lxx = plP_stsearch( xopt, 'x' );

    lay = plP_stsearch( yopt, 'a' );
    lby = plP_stsearch( yopt, 'b' );
    lcy = plP_stsearch( yopt, 'c' );
    ldy = plP_stsearch( yopt, 'd' );
    lgy = plP_stsearch( yopt, 'g' );
    liy = plP_stsearch( yopt, 'i' );
    lly = plP_stsearch( yopt, 'l' );
    lsy = plP_stsearch( yopt, 's' );
    lty = plP_stsearch( yopt, 't' );
    luy = plP_stsearch( yopt, 'u' );
    lwy = plP_stsearch( yopt, 'w' );
    lxy = plP_stsearch( yopt, 'x' );

// Tick and subtick sizes in device coords

    xmajor = MAX( ROUND( plsc->majht * plsc->ypmm ), 1 );
    ymajor = MAX( ROUND( plsc->majht * plsc->xpmm ), 1 );
    xminor = MAX( ROUND( plsc->minht * plsc->ypmm ), 1 );
    yminor = MAX( ROUND( plsc->minht * plsc->xpmm ), 1 );

    nxsub1 = nxsub;
    nysub1 = nysub;
    xtick1 = llx ? 1.0 : xtick;
    ytick1 = lly ? 1.0 : ytick;

    plP_xgvpw( &vpwxmin, &vpwxmax, &vpwymin, &vpwymax );
// vpwxmi always numerically less than vpwxma, and
// similarly for vpwymi
    vpwxmi = ( vpwxmax > vpwxmin ) ? vpwxmin : vpwxmax;
    vpwxma = ( vpwxmax > vpwxmin ) ? vpwxmax : vpwxmin;
    vpwymi = ( vpwymax > vpwymin ) ? vpwymin : vpwymax;
    vpwyma = ( vpwymax > vpwymin ) ? vpwymax : vpwymin;

// Plot axes only if they are inside viewport.
    lax = lax && vpwymi < y0 && y0 < vpwyma;
    lay = lay && vpwxmi < x0 && x0 < vpwxma;

// Calculate tick spacing

    if ( ltx || lgx || lxx )
        pldtik( vpwxmi, vpwxma, &xtick1, &nxsub1, ldx );

    if ( lty || lgy || lxy )
        pldtik( vpwymi, vpwyma, &ytick1, &nysub1, ldy );
// n.b. large change; xtick1, nxsub1, ytick1, nysub1 always positive.

// Set up tick variables

    if ( lix )
    {
        i1x = xminor;
        i2x = 0;
        i3x = xmajor;
        i4x = 0;
    }
    else
    {
        i1x = 0;
        i2x = xminor;
        i3x = 0;
        i4x = xmajor;
    }

    if ( liy )
    {
        i1y = yminor;
        i2y = 0;
        i3y = ymajor;
        i4y = 0;
    }
    else
    {
        i1y = 0;
        i2y = yminor;
        i3y = 0;
        i4y = ymajor;
    }

    if ( plsc->if_boxbb )
    {
        // Carefully follow logic below (and above) for the case where
        // an inverted major tick mark is written (in the X direction
        // for a Y axis and vice versa).  Ignore minor tick marks
        // which are assumed to be smaller.  Ignore axes and grids
        // which are all contained within the viewport.
        if ( lix && ( lbx || lux ) && ( ltx && !lxx ) )
            plsc->boxbb_ymin -= xmajor / plsc->ypmm;
        if ( liy && ( lcy || lwy ) && ( lty && !lxy ) )
            plsc->boxbb_xmax += ymajor / plsc->xpmm;
        if ( lix && ( lcx || lwx ) && ( ltx && !lxx ) )
            plsc->boxbb_ymax += xmajor / plsc->ypmm;
        if ( liy && ( lby || luy ) && ( lty && !lxy ) )
            plsc->boxbb_xmin -= ymajor / plsc->xpmm;
    }
    else
    {
// Draw the bottom frame of the box

        if ( lbx || lux )
        {
            if ( !lux )
            {
                plP_movphy( vppxmi, vppymi );
                plP_draphy( vppxma, vppymi );
            }
            if ( ltx && !lxx )
            {
                if ( ldx )
                {
                    pldtfac( vpwxmi, vpwxma, &factor, &tstart );
                    tp = xtick1 * ( floor( ( vpwxmi - tstart ) / xtick1 ) ) + tstart;
                }
                else
                    tp = xtick1 * floor( vpwxmi / xtick1 );
                for (;; )
                {
                    tn = tp + xtick1;
                    if ( lsx )
                    {
                        if ( llx )
                        {
                            for ( i = 0; i <= 7; i++ )
                            {
                                temp = tp + xlog[i];
                                if ( BETW( temp, vpwxmi, vpwxma ) )
                                    plxtik( plP_wcpcx( temp ), vppymi, i1x, i2x );
                            }
                        }
                        else
                        {
                            for ( i = 1; i <= nxsub1 - 1; i++ )
                            {
                                temp = tp + i * xtick1 / nxsub1;
                                if ( BETW( temp, vpwxmi, vpwxma ) )
                                    plxtik( plP_wcpcx( temp ), vppymi, i1x, i2x );
                            }
                        }
                    }
                    if ( !BETW( tn, vpwxmi, vpwxma ) )
                        break;
                    plxtik( plP_wcpcx( tn ), vppymi, i3x, i4x );
                    tp = tn;
                }
            }
        }

// Draw the right-hand frame of box

        if ( lcy || lwy )
        {
            if ( !lwy )
            {
                plP_movphy( vppxma, vppymi );
                plP_draphy( vppxma, vppyma );
            }
            if ( lty && !lxy )
            {
                if ( ldy )
                {
                    pldtfac( vpwymi, vpwyma, &factor, &tstart );
                    tp = ytick1 * ( floor( ( vpwymi - tstart ) / ytick1 ) ) + tstart;
                }
                else
                    tp = ytick1 * floor( vpwymi / ytick1 );
                for (;; )
                {
                    tn = tp + ytick1;
                    if ( lsy )
                    {
                        if ( lly )
                        {
                            for ( i = 0; i <= 7; i++ )
                            {
                                temp = tp + xlog[i];
                                if ( BETW( temp, vpwymi, vpwyma ) )
                                    plytik( vppxma, plP_wcpcy( temp ), i2y, i1y );
                            }
                        }
                        else
                        {
                            for ( i = 1; i <= nysub1 - 1; i++ )
                            {
                                temp = tp + i * ytick1 / nysub1;
                                if ( BETW( temp, vpwymi, vpwyma ) )
                                    plytik( vppxma, plP_wcpcy( temp ), i2y, i1y );
                            }
                        }
                    }
                    if ( !BETW( tn, vpwymi, vpwyma ) )
                        break;
                    plytik( vppxma, plP_wcpcy( tn ), i4y, i3y );
                    tp = tn;
                }
            }
        }

// Draw the top frame of the box

        if ( lcx || lwx )
        {
            if ( !lwx )
            {
                plP_movphy( vppxma, vppyma );
                plP_draphy( vppxmi, vppyma );
            }
            if ( ltx && !lxx )
            {
                if ( ldx )
                {
                    pldtfac( vpwxmi, vpwxma, &factor, &tstart );
                    tp = xtick1 * ( floor( ( vpwxma - tstart ) / xtick1 ) + 1 ) + tstart;
                }
                else
                    tp = xtick1 * ( floor( vpwxma / xtick1 ) + 1 );
                for (;; )
                {
                    tn = tp - xtick1;
                    if ( lsx )
                    {
                        if ( llx )
                        {
                            for ( i = 7; i >= 0; i-- )
                            {
                                temp = tn + xlog[i];
                                if ( BETW( temp, vpwxmi, vpwxma ) )
                                    plxtik( plP_wcpcx( temp ), vppyma, i2x, i1x );
                            }
                        }
                        else
                        {
                            for ( i = nxsub1 - 1; i >= 1; i-- )
                            {
                                temp = tn + i * xtick1 / nxsub1;
                                if ( BETW( temp, vpwxmi, vpwxma ) )
                                    plxtik( plP_wcpcx( temp ), vppyma, i2x, i1x );
                            }
                        }
                    }
                    if ( !BETW( tn, vpwxmi, vpwxma ) )
                        break;
                    plxtik( plP_wcpcx( tn ), vppyma, i4x, i3x );
                    tp = tn;
                }
            }
        }

// Draw the left-hand frame of box

        if ( lby || luy )
        {
            if ( !luy )
            {
                plP_movphy( vppxmi, vppyma );
                plP_draphy( vppxmi, vppymi );
            }
            if ( lty && !lxy )
            {
                if ( ldy )
                {
                    pldtfac( vpwymi, vpwyma, &factor, &tstart );
                    tp = ytick1 * ( floor( ( vpwyma - tstart ) / ytick1 ) + 1 ) + tstart;
                }
                else
                    tp = ytick1 * ( floor( vpwyma / ytick1 ) + 1 );
                for (;; )
                {
                    tn = tp - ytick1;
                    if ( lsy )
                    {
                        if ( lly )
                        {
                            for ( i = 7; i >= 0; i-- )
                            {
                                temp = tn + xlog[i];
                                if ( BETW( temp, vpwymi, vpwyma ) )
                                    plytik( vppxmi, plP_wcpcy( temp ), i1y, i2y );
                            }
                        }
                        else
                        {
                            for ( i = nysub1 - 1; i >= 1; i-- )
                            {
                                temp = tn + i * ytick1 / nysub1;
                                if ( BETW( temp, vpwymi, vpwyma ) )
                                    plytik( vppxmi, plP_wcpcy( temp ), i1y, i2y );
                            }
                        }
                    }
                    if ( !BETW( tn, vpwymi, vpwyma ) )
                        break;
                    plytik( vppxmi, plP_wcpcy( tn ), i3y, i4y );
                    tp = tn;
                }
            }
        }

        // Draw the horizontal axis.
        if ( lax )
        {
            // Convert world coordinates to physical
            yp0 = plP_wcpcy( y0 );
            plP_movphy( vppxmi, (PLINT) yp0 );
            plP_draphy( vppxma, (PLINT) yp0 );
            if ( ltx && !lxx )
            {
                tp = xtick1 * floor( vpwxmi / xtick1 );
                for (;; )
                {
                    tn = tp + xtick1;
                    if ( lsx )
                    {
                        if ( llx )
                        {
                            for ( i = 0; i <= 7; i++ )
                            {
                                temp = tp + xlog[i];
                                if ( BETW( temp, vpwxmi, vpwxma ) )
                                    plxtik( plP_wcpcx( temp ), (PLINT) yp0, xminor, xminor );
                            }
                        }
                        else
                        {
                            for ( i = 1; i <= nxsub1 - 1; i++ )
                            {
                                temp = tp + i * xtick1 / nxsub1;
                                if ( BETW( temp, vpwxmi, vpwxma ) )
                                    plxtik( plP_wcpcx( temp ), (PLINT) yp0, xminor, xminor );
                            }
                        }
                    }
                    if ( !BETW( tn, vpwxmi, vpwxma ) )
                        break;
                    plxtik( plP_wcpcx( tn ), (PLINT) yp0, xmajor, xmajor );
                    tp = tn;
                }
            }
        }

        // Draw the vertical axis.
        if ( lay )
        {
            // Convert world coordinates to physical
            xp0 = plP_wcpcx( x0 );
            plP_movphy( (PLINT) xp0, vppymi );
            plP_draphy( (PLINT) xp0, vppyma );
            if ( lty && !lxy )
            {
                tp = ytick1 * floor( vpwymi / ytick1 );
                for (;; )
                {
                    tn = tp + ytick1;
                    if ( lsy )
                    {
                        if ( lly )
                        {
                            for ( i = 0; i <= 7; i++ )
                            {
                                temp = tp + xlog[i];
                                if ( BETW( temp, vpwymi, vpwyma ) )
                                    plytik( (PLINT) xp0, plP_wcpcy( temp ), yminor, yminor );
                            }
                        }
                        else
                        {
                            for ( i = 1; i <= nysub1 - 1; i++ )
                            {
                                temp = tp + i * ytick1 / nysub1;
                                if ( BETW( temp, vpwymi, vpwyma ) )
                                    plytik( (PLINT) xp0, plP_wcpcy( temp ), yminor, yminor );
                            }
                        }
                    }
                    if ( !BETW( tn, vpwymi, vpwyma ) )
                        break;
                    plytik( (PLINT) xp0, plP_wcpcy( tn ), ymajor, ymajor );
                    tp = tn;
                }
            }
        }

        // Draw grids.
        grid_box( xopt, xtick1, nxsub1, yopt, ytick1, nysub1 );
    }

    // Write labels.
    label_box( xopt, xtick1, yopt, ytick1 );

// Restore the clip limits to viewport edge

    plP_sclp( lxmin, lxmax, lymin, lymax );
}

//--------------------------------------------------------------------------
// void plbox3()
//
// This is the 3-d analogue of plbox().
//--------------------------------------------------------------------------

void
c_plbox3( const char *xopt, const char *xlabel, PLFLT xtick, PLINT nxsub,
          const char *yopt, const char *ylabel, PLFLT ytick, PLINT nysub,
          const char *zopt, const char *zlabel, PLFLT ztick, PLINT nzsub )
{
    PLFLT dx, dy, tx, ty, ux, uy;
    PLFLT xmin, xmax, ymin, ymax, zmin, zmax, zscale;
    PLFLT cxx, cxy, cyx, cyy, cyz;
    PLINT ln;
    PLINT *zbflg, *zbcol;
    PLFLT *zbwidth;
    PLFLT *zbtck;
    PLINT xdigmax, xdigits;
    PLINT ydigmax, ydigits;
    PLINT zdigmax, zdigits;

    if ( plsc->level < 3 )
    {
        plabort( "plbox3: Please set up window first" );
        return;
    }

    plP_gw3wc( &cxx, &cxy, &cyx, &cyy, &cyz );
    plP_gdom( &xmin, &xmax, &ymin, &ymax );
    plP_grange( &zscale, &zmin, &zmax );

    plgxax( &xdigmax, &xdigits );
    plgyax( &ydigmax, &ydigits );
    plgzax( &zdigmax, &zdigits );

    xdigits = xdigmax;
    ydigits = ydigmax;
    zdigits = zdigmax;

// We have to wait until after the plot is drawn to draw back
// grid so store this stuff.

    plP_gzback( &zbflg, &zbcol, &zbtck, &zbwidth );
    *zbflg = plP_stsearch( zopt, 'd' );
    if ( *zbflg )
    {
        *zbtck   = ztick;       // save tick spacing
        *zbcol   = plsc->icol0; // and color
        *zbwidth = plsc->width; // and line width
    }

    if ( cxx >= 0.0 && cxy <= 0.0 )
    {
        ln = plP_stsearch( xopt, 'n' );
        tx = plP_w3wcx( xmin, ymin, zmin );
        ty = plP_w3wcy( xmin, ymin, zmin );
        ux = plP_w3wcx( xmax, ymin, zmin );
        uy = plP_w3wcy( xmax, ymin, zmin );
        plxybx( xopt, xlabel, PL_X_AXIS, tx, ty, ux, uy,
            xmin, xmax, xtick, nxsub, 0, &xdigits );

        dx = ux - tx;
        dy = uy - ty;
        plzbx( zopt, zlabel, 1, dx, dy, ux, uy,
            plP_w3wcy( xmax, ymin, zmax ), zmin, zmax, ztick, nzsub, &zdigits );

        tx = plP_w3wcx( xmin, ymax, zmin );
        ty = plP_w3wcy( xmin, ymax, zmin );
        ux = plP_w3wcx( xmin, ymin, zmin );
        uy = plP_w3wcy( xmin, ymin, zmin );
        plxybx( yopt, ylabel, PL_Y_AXIS, tx, ty, ux, uy,
            ymax, ymin, ytick, nysub, ln, &ydigits );

        dx = ux - tx;
        dy = uy - ty;
// restore zdigits to initial value for second call
        zdigits = zdigmax;
        plzbx( zopt, zlabel, 0, dx, dy, tx, ty,
            plP_w3wcy( xmin, ymax, zmax ), zmin, zmax, ztick, nzsub, &zdigits );
    }
    else if ( cxx <= 0.0 && cxy <= 0.0 )
    {
        ln = plP_stsearch( yopt, 'n' );
        tx = plP_w3wcx( xmin, ymax, zmin );
        ty = plP_w3wcy( xmin, ymax, zmin );
        ux = plP_w3wcx( xmin, ymin, zmin );
        uy = plP_w3wcy( xmin, ymin, zmin );
        plxybx( yopt, ylabel, PL_Y_AXIS, tx, ty, ux, uy,
            ymax, ymin, ytick, nysub, 0, &ydigits );

        dx = ux - tx;
        dy = uy - ty;
        plzbx( zopt, zlabel, 1, dx, dy, ux, uy,
            plP_w3wcy( xmin, ymin, zmax ), zmin, zmax, ztick, nzsub, &zdigits );

        tx = plP_w3wcx( xmax, ymax, zmin );
        ty = plP_w3wcy( xmax, ymax, zmin );
        ux = plP_w3wcx( xmin, ymax, zmin );
        uy = plP_w3wcy( xmin, ymax, zmin );
        plxybx( xopt, xlabel, PL_X_AXIS, tx, ty, ux, uy,
            xmax, xmin, xtick, nxsub, ln, &xdigits );

        dx = ux - tx;
        dy = uy - ty;
// restore zdigits to initial value for second call
        zdigits = zdigmax;
        plzbx( zopt, zlabel, 0, dx, dy, tx, ty,
            plP_w3wcy( xmax, ymax, zmax ), zmin, zmax, ztick, nzsub, &zdigits );
    }
    else if ( cxx <= 0.0 && cxy >= 0.0 )
    {
        ln = plP_stsearch( xopt, 'n' );
        tx = plP_w3wcx( xmax, ymax, zmin );
        ty = plP_w3wcy( xmax, ymax, zmin );
        ux = plP_w3wcx( xmin, ymax, zmin );
        uy = plP_w3wcy( xmin, ymax, zmin );
        plxybx( xopt, xlabel, PL_X_AXIS, tx, ty, ux, uy,
            xmax, xmin, xtick, nxsub, 0, &xdigits );

        dx = ux - tx;
        dy = uy - ty;
        plzbx( zopt, zlabel, 1, dx, dy, ux, uy,
            plP_w3wcy( xmin, ymax, zmax ), zmin, zmax, ztick, nzsub, &zdigits );

        tx = plP_w3wcx( xmax, ymin, zmin );
        ty = plP_w3wcy( xmax, ymin, zmin );
        ux = plP_w3wcx( xmax, ymax, zmin );
        uy = plP_w3wcy( xmax, ymax, zmin );
        plxybx( yopt, ylabel, PL_Y_AXIS, tx, ty, ux, uy,
            ymin, ymax, ytick, nysub, ln, &ydigits );

        dx = ux - tx;
        dy = uy - ty;
// restore zdigits to initial value for second call
        zdigits = zdigmax;
        plzbx( zopt, zlabel, 0, dx, dy, tx, ty,
            plP_w3wcy( xmax, ymin, zmax ), zmin, zmax, ztick, nzsub, &zdigits );
    }
    else if ( cxx >= 0.0 && cxy >= 0.0 )
    {
        ln = plP_stsearch( yopt, 'n' );
        tx = plP_w3wcx( xmax, ymin, zmin );
        ty = plP_w3wcy( xmax, ymin, zmin );
        ux = plP_w3wcx( xmax, ymax, zmin );
        uy = plP_w3wcy( xmax, ymax, zmin );
        plxybx( yopt, ylabel, PL_X_AXIS, tx, ty, ux, uy,
            ymin, ymax, ytick, nysub, 0, &ydigits );

        dx = ux - tx;
        dy = uy - ty;
        plzbx( zopt, zlabel, 1, dx, dy, ux, uy,
            plP_w3wcy( xmax, ymax, zmax ), zmin, zmax, ztick, nzsub, &zdigits );

        tx = plP_w3wcx( xmin, ymin, zmin );
        ty = plP_w3wcy( xmin, ymin, zmin );
        ux = plP_w3wcx( xmax, ymin, zmin );
        uy = plP_w3wcy( xmax, ymin, zmin );
        plxybx( xopt, xlabel, PL_X_AXIS, tx, ty, ux, uy,
            xmin, xmax, xtick, nxsub, ln, &xdigits );

        dx = ux - tx;
        dy = uy - ty;
// restore zdigits to initial value for second call
        zdigits = zdigmax;
        plzbx( zopt, zlabel, 0, dx, dy, tx, ty,
            plP_w3wcy( xmin, ymin, zmax ), zmin, zmax, ztick, nzsub, &zdigits );
    }
    plsxax( xdigmax, xdigits );
    plsyax( ydigmax, ydigits );
    plszax( zdigmax, zdigits );
}

//--------------------------------------------------------------------------
// Support routines for 3d box draw.
//--------------------------------------------------------------------------

//--------------------------------------------------------------------------
// void plxybx()
//
// This draws a sloping line from (wx1,wy1) to (wx2,wy2) which represents an
// axis of a 3-d graph with data values from "vmin" to "vmax". Depending on
// "opt", vertical ticks and/or subticks are placed on the line at major tick
// interval "tick" with "nsub" subticks between major ticks. If "tick" and/or
// "nsub" is zero, automatic tick positions are computed
//
// b: Draw box boundary
// f: Always use fixed point numeric labels
// i: Inverts tick marks (i.e. drawn downwards)
// l: Logarithmic axes, major ticks at decades, minor ticks at units
// n: Write numeric label
// o: Use custom label function
// t: Draw major tick marks
// s: Draw minor tick marks
// u: Write label on line
//--------------------------------------------------------------------------

static void
plxybx( const char *opt, const char *label, PLINT axis, PLFLT wx1, PLFLT wy1,
        PLFLT wx2, PLFLT wy2, PLFLT vmin_in, PLFLT vmax_in,
        PLFLT tick, PLINT nsub, PLINT PL_UNUSED( nolast ), PLINT *digits )
{
    static char string[STRING_LEN];
    PLINT       lb, ld, lf, li, ll, ln, ls, lt, lu, lo;
    PLINT       major, minor, mode, prec, scale;
    PLINT       i, i1, i2, i3, i4;
    PLINT       nsub1;
    PLFLT       pos, tn, tp, temp, height, tick1, vmin, vmax;
// Note that 'tspace' is the minimim distance away (in fractional number
// of ticks) from the boundary that an X or Y numerical label can be drawn.
    PLFLT      dwx, dwy, lambda, tcrit, tspace = 0.1;
    const char * esc_string = plgesc_string();

    vmin = ( vmax_in > vmin_in ) ? vmin_in : vmax_in;
    vmax = ( vmax_in > vmin_in ) ? vmax_in : vmin_in;

    dwx = wx2 - wx1;
    dwy = wy2 - wy1;

// Tick and subtick sizes in device coords

    major = MAX( ROUND( plsc->majht * plsc->ypmm ), 1 );
    minor = MAX( ROUND( plsc->minht * plsc->ypmm ), 1 );

    tick1 = tick;
    nsub1 = nsub;

    lb = plP_stsearch( opt, 'b' );
    ld = plP_stsearch( opt, 'd' );
    lf = plP_stsearch( opt, 'f' );
    li = plP_stsearch( opt, 'i' );
    ll = plP_stsearch( opt, 'l' );
    ln = plP_stsearch( opt, 'n' );
    ls = plP_stsearch( opt, 's' );
    lt = plP_stsearch( opt, 't' );
    lu = plP_stsearch( opt, 'u' );
    lo = plP_stsearch( opt, 'o' );

    if ( lu )
        plxytx( wx1, wy1, wx2, wy2, 3.2, 0.5, 0.5, label );
    if ( !lb )
        return;

    if ( ll )
        tick1 = ( vmax > vmin ) ? 1.0 : -1.0;
    if ( lt )
        pldtik( vmin, vmax, &tick1, &nsub1, ld );

    if ( li )
    {
        i1 = minor;
        i2 = 0;
        i3 = major;
        i4 = 0;
    }
    else
    {
        i1 = 0;
        i2 = minor;
        i3 = 0;
        i4 = major;
    }

// Draw the line

    plP_movwor( wx1, wy1 );
    plP_drawor( wx2, wy2 );
    if ( lt )
    {
        tp = tick1 * floor( vmin / tick1 );
        for (;; )
        {
            tn = tp + tick1;
            if ( ls )
            {
                if ( ll )
                {
                    for ( i = 0; i <= 7; i++ )
                    {
                        temp = tp + xlog[i];
                        if ( BETW( temp, vmin, vmax ) )
                        {
                            lambda = ( vmax_in > vmin_in ) ?
                                     ( temp - vmin ) / ( vmax - vmin ) :
                                     ( vmax - temp ) / ( vmax - vmin );
                            plxtik( plP_wcpcx( (PLFLT) ( wx1 + lambda * dwx ) ),
                                plP_wcpcy( (PLFLT) ( wy1 + lambda * dwy ) ),
                                i1, i2 );
                        }
                    }
                }
                else
                {
                    for ( i = 1; i <= nsub1 - 1; i++ )
                    {
                        temp = tp + i * ( tn - tp ) / nsub1;
                        if ( BETW( temp, vmin, vmax ) )
                        {
                            lambda = ( vmax_in > vmin_in ) ?
                                     ( temp - vmin ) / ( vmax - vmin ) :
                                     ( vmax - temp ) / ( vmax - vmin );
                            plxtik( plP_wcpcx( (PLFLT) ( wx1 + lambda * dwx ) ),
                                plP_wcpcy( (PLFLT) ( wy1 + lambda * dwy ) ),
                                i1, i2 );
                        }
                    }
                }
            }
            temp = tn;
            if ( !BETW( temp, vmin, vmax ) )
                break;

            lambda = ( vmax_in > vmin_in ) ?
                     ( temp - vmin ) / ( vmax - vmin ) :
                     ( vmax - temp ) / ( vmax - vmin );
            plxtik( plP_wcpcx( (PLFLT) ( wx1 + lambda * dwx ) ),
                plP_wcpcy( (PLFLT) ( wy1 + lambda * dwy ) ), i3, i4 );
            tp = tn;
        }
    }


// Label the line

    if ( ln && lt )
    {
        pldprec( vmin, vmax, tick1, lf, &mode, &prec, *digits, &scale );
        pos    = 1.0;
        height = 3.2;
        tcrit  = tspace * tick1;
        tp     = tick1 * ( 1. + floor( vmin / tick1 ) );
        for ( tn = tp; BETW( tn, vmin, vmax ); tn += tick1 )
        {
            if ( BETW( tn, vmin + tcrit, vmax - tcrit ) )
            {
                plform( axis, tn, scale, prec, string, STRING_LEN, ll, lf, lo );
                pos = ( vmax_in > vmin_in ) ?
                      ( tn - vmin ) / ( vmax - vmin ) :
                      ( vmax - tn ) / ( vmax - vmin );
                plxytx( wx1, wy1, wx2, wy2, 1.5, pos, 0.5, string );
            }
        }
        *digits = 2;
        if ( !ll && !lo && mode )
        {
            snprintf( string, STRING_LEN, "(x10%su%d%sd)", esc_string, (int) scale, esc_string );
            plxytx( wx1, wy1, wx2, wy2, height, 1.0, 0.5, string );
        }
    }
}

//--------------------------------------------------------------------------
// void plxytx()
//
// Prints out text along a sloping axis joining world coordinates
// (wx1,wy1) to (wx2,wy2). Parameters are as for plmtext.
//--------------------------------------------------------------------------

static void
plxytx( PLFLT wx1, PLFLT wy1, PLFLT wx2, PLFLT wy2,
        PLFLT disp, PLFLT pos, PLFLT just, const char *text )
{
    PLINT x, y, refx, refy;
    PLFLT shift, cc, ss, wx, wy;
    PLFLT xdv, ydv, xmm, ymm, refxmm, refymm, xform[4], diag;
    PLFLT dispx, dispy;
    PLFLT chrdef, chrht;

    cc   = plsc->wmxscl * ( wx2 - wx1 );
    ss   = plsc->wmyscl * ( wy2 - wy1 );
    diag = sqrt( cc * cc + ss * ss );
    cc  /= diag;
    ss  /= diag;
    wx   = wx1 + pos * ( wx2 - wx1 );
    wy   = wy1 + pos * ( wy2 - wy1 );

    xform[0] = cc;
    xform[1] = 0.0;
    xform[2] = ss;
    xform[3] = 1.0;

    xdv = plP_wcdcx( wx );
    ydv = plP_wcdcy( wy );

    dispx = 0.;
    dispy = -disp;

    plgchr( &chrdef, &chrht );
    shift = ( just == 0.0 ) ? 0.0 : plstrl( text ) * just;

    xmm    = plP_dcmmx( xdv ) + dispx * chrht;
    ymm    = plP_dcmmy( ydv ) + dispy * chrht;
    refxmm = xmm - shift * xform[0];
    refymm = ymm - shift * xform[2];

    x    = plP_mmpcx( xmm );
    y    = plP_mmpcy( ymm );
    refx = plP_mmpcx( refxmm );
    refy = plP_mmpcy( refymm );

    plP_text( 0, just, xform, x, y, refx, refy, text );
}

//--------------------------------------------------------------------------
// void plzbx()
//
// This draws a vertical line from (wx,wy1) to (wx,wy2) which represents the
// vertical axis of a 3-d graph with data values from "vmin" to "vmax".
// Depending on "opt", ticks and/or subticks are placed on the line at major
// tick interval "tick" with "nsub" subticks between major ticks. If "tick"
// and/or "nsub" is zero, automatic tick positions are computed
//
// b: Draws left-hand axis
// c: Draws right-hand axis
// f: Always use fixed point numeric labels
// i: Inverts tick marks (i.e. drawn to the left)
// l: Logarithmic axes, major ticks at decades, minor ticks at units
// m: Write numeric label on right axis
// n: Write numeric label on left axis
// o: Use custom label function
// s: Draw minor tick marks
// t: Draw major tick marks
// u: Writes left-hand label
// v: Writes right-hand label
//--------------------------------------------------------------------------

static void
plzbx( const char *opt, const char *label, PLINT right, PLFLT dx, PLFLT dy,
       PLFLT wx, PLFLT wy1, PLFLT wy2, PLFLT vmin_in, PLFLT vmax_in,
       PLFLT tick, PLINT nsub, PLINT *digits )
{
    static char string[STRING_LEN];
    PLINT       lb, lc, ld, lf, li, ll, lm, ln, ls, lt, lu, lv, lo;
    PLINT       i, mode, prec, scale;
    PLINT       nsub1, lstring;
    PLFLT       pos, tn, tp, temp, height, tick1;
    PLFLT       dwy, lambda, diag, major, minor, xmajor, xminor;
    PLFLT       ymajor, yminor, dxm, dym, vmin, vmax;
    const char  * esc_string = plgesc_string();

    vmin = ( vmax_in > vmin_in ) ? vmin_in : vmax_in;
    vmax = ( vmax_in > vmin_in ) ? vmax_in : vmin_in;

    dwy = wy2 - wy1;

// Tick and subtick sizes in device coords

    major = plsc->majht;
    minor = plsc->minht;

    tick1 = tick;
    nsub1 = nsub;

    lb = plP_stsearch( opt, 'b' );
    lc = plP_stsearch( opt, 'c' );
    ld = plP_stsearch( opt, 'd' );
    lf = plP_stsearch( opt, 'f' );
    li = plP_stsearch( opt, 'i' );
    ll = plP_stsearch( opt, 'l' );
    lm = plP_stsearch( opt, 'm' );
    ln = plP_stsearch( opt, 'n' );
    ls = plP_stsearch( opt, 's' );
    lt = plP_stsearch( opt, 't' );
    lu = plP_stsearch( opt, 'u' );
    lv = plP_stsearch( opt, 'v' );
    lo = plP_stsearch( opt, 'o' );

    if ( lu && !right )
        plztx( "h", dx, dy, wx, wy1, wy2, 5.0, 0.5, 0.5, label );

    if ( lv && right )
        plztx( "h", dx, dy, wx, wy1, wy2, -5.0, 0.5, 0.5, label );

    if ( right && !lc )
        return;

    if ( !right && !lb )
        return;

    if ( ll )
        tick1 = 1.0;

    if ( lt )
        pldtik( vmin, vmax, &tick1, &nsub1, ld );

    if ( ( li && !right ) || ( !li && right ) )
    {
        minor = -minor;
        major = -major;
    }

    dxm  = dx * plsc->wmxscl;
    dym  = dy * plsc->wmyscl;
    diag = sqrt( dxm * dxm + dym * dym );

    xminor = minor * dxm / diag;
    xmajor = major * dxm / diag;
    yminor = minor * dym / diag;
    ymajor = major * dym / diag;

// Draw the line

    plP_movwor( wx, wy1 );
    plP_drawor( wx, wy2 );
    if ( lt )
    {
        tp = tick1 * floor( vmin / tick1 );
        for (;; )
        {
            tn = tp + tick1;
            if ( ls )
            {
                if ( ll )
                {
                    for ( i = 0; i <= 7; i++ )
                    {
                        temp = tp + xlog[i];
                        if ( BETW( temp, vmin, vmax ) )
                        {
                            lambda = ( vmax_in > vmin_in ) ?
                                     ( temp - vmin ) / ( vmax - vmin ) :
                                     ( vmax - temp ) / ( vmax - vmin );
                            plstik( plP_wcmmx( wx ),
                                plP_wcmmy( (PLFLT) ( wy1 + lambda * dwy ) ),
                                xminor, yminor );
                        }
                    }
                }
                else
                {
                    for ( i = 1; i <= nsub1 - 1; i++ )
                    {
                        temp = tp + i * tick1 / nsub1;
                        if ( BETW( temp, vmin, vmax ) )
                        {
                            lambda = ( vmax_in > vmin_in ) ?
                                     ( temp - vmin ) / ( vmax - vmin ) :
                                     ( vmax - temp ) / ( vmax - vmin );
                            plstik( plP_wcmmx( wx ),
                                plP_wcmmy( (PLFLT) ( wy1 + lambda * dwy ) ),
                                xminor, yminor );
                        }
                    }
                }
            }
            temp = tn;
            if ( !BETW( temp, vmin, vmax ) )
                break;
            lambda = ( vmax_in > vmin_in ) ?
                     ( temp - vmin ) / ( vmax - vmin ) :
                     ( vmax - temp ) / ( vmax - vmin );
            plstik( plP_wcmmx( wx ), plP_wcmmy( (PLFLT) ( wy1 + lambda * dwy ) ),
                xmajor, ymajor );
            tp = tn;
        }
    }


// Label the line

    if ( ( ln || lm ) && lt )
    {
        pldprec( vmin, vmax, tick1, lf, &mode, &prec, *digits, &scale );
        *digits = 0;
        tp      = tick1 * floor( vmin / tick1 );
        for ( tn = tp + tick1; BETW( tn, vmin, vmax ); tn += tick1 )
        {
            plform( PL_Z_AXIS, tn, scale, prec, string, STRING_LEN, ll, lf, lo );
            pos = ( vmax_in > vmin_in ) ?
                  ( tn - vmin ) / ( vmax - vmin ) :
                  ( vmax - tn ) / ( vmax - vmin );
            if ( ln && !right )
                plztx( "v", dx, dy, wx, wy1, wy2, 0.5, pos, 1.0, string );

            if ( lm && right )
                plztx( "v", dx, dy, wx, wy1, wy2, -0.5, pos, 0.0, string );

            lstring = (PLINT) strlen( string );
            *digits = MAX( *digits, lstring );
        }
        if ( !ll && !lo && mode )
        {
            snprintf( string, STRING_LEN, "(x10%su%d%sd)", esc_string, (int) scale, esc_string );
            pos    = 1.15;
            height = 0.5;
            if ( ln && !right )
            {
                plztx( "v", dx, dy, wx, wy1, wy2, height, pos, 0.5, string );
            }
            if ( lm && right )
            {
                plztx( "v", dx, dy, wx, wy1, wy2,
                    (PLFLT) -height, pos, 0.5, string );
            }
        }
    }
}

//--------------------------------------------------------------------------
// void plztx()
//
// Prints out text along a vertical axis for a 3d plot joining
// world coordinates (wx,wy1) to (wx,wy2).
//--------------------------------------------------------------------------

static void
plztx( const char *opt, PLFLT dx, PLFLT dy, PLFLT wx, PLFLT wy1,
       PLFLT wy2, PLFLT disp, PLFLT pos, PLFLT just, const char *text )
{
    PLINT refx = 0, refy = 0, x = 0, y = 0, vert = 0;
    PLFLT shift, cc, ss, wy;
    PLFLT xdv, ydv, xmm, ymm, refxmm, refymm, xform[4], diag;
    PLFLT dispx, dispy;
    PLFLT chrdef, chrht;

    cc   = plsc->wmxscl * dx;
    ss   = plsc->wmyscl * dy;
    diag = sqrt( cc * cc + ss * ss );
    cc  /= diag;
    ss  /= diag;
    wy   = wy1 + pos * ( wy2 - wy1 );

    if ( plP_stsearch( opt, 'v' ) )
        vert = 0;
    else if ( plP_stsearch( opt, 'h' ) )
        vert = 1;

    if ( vert )
    {
        xform[0] = 0.0;
        xform[1] = -cc;
        xform[2] = 1.0;
        xform[3] = -ss;
    }
    else
    {
        xform[0] = cc;
        xform[1] = 0.0;
        xform[2] = ss;
        xform[3] = 1.0;
    }

    xdv = plP_wcdcx( wx );
    ydv = plP_wcdcy( wy );

    dispx = -disp * cc;
    dispy = -disp * ss;

    plgchr( &chrdef, &chrht );
    shift = ( just == 0.0 ) ? 0.0 : plstrl( text ) * just;

    xmm    = plP_dcmmx( xdv ) + dispx * chrht;
    ymm    = plP_dcmmy( ydv ) + dispy * chrht;
    refxmm = xmm - shift * xform[0];
    refymm = ymm - shift * xform[2];

    x    = plP_mmpcx( xmm );
    y    = plP_mmpcy( ymm );
    refx = plP_mmpcx( refxmm );
    refy = plP_mmpcy( refymm );

    plP_text( 0, just, xform, x, y, refx, refy, text );
}

//--------------------------------------------------------------------------
// void grid_box()
//
// Draws grids at tick locations (major and/or minor).
//
// Note that 'tspace' is the minimim distance away (in fractional number
// of ticks or subticks) from the boundary a grid line can be drawn.  If
// you are too close, it looks bad.
//--------------------------------------------------------------------------

static void
grid_box( const char *xopt, PLFLT xtick1, PLINT nxsub1,
          const char *yopt, PLFLT ytick1, PLINT nysub1 )
{
    PLINT lgx, lhx, llx, ldx;
    PLINT lgy, lhy, lly, ldy;
    PLFLT vpwxmi, vpwxma, vpwymi, vpwyma;
    PLFLT vpwxmin, vpwxmax, vpwymin, vpwymax;
    PLFLT tn, temp, tcrit, tspace = 0.1;
    PLFLT tstart, factor;
    PLINT i;

// Set plot options from input

    lgx = plP_stsearch( xopt, 'g' );
    lhx = plP_stsearch( xopt, 'h' );
    llx = plP_stsearch( xopt, 'l' );
    ldx = plP_stsearch( xopt, 'd' );

    lgy = plP_stsearch( yopt, 'g' );
    lhy = plP_stsearch( yopt, 'h' );
    lly = plP_stsearch( yopt, 'l' );
    ldy = plP_stsearch( yopt, 'd' );

    plP_xgvpw( &vpwxmin, &vpwxmax, &vpwymin, &vpwymax );
// n.b. large change; vpwxmi always numerically less than vpwxma, and
// similarly for vpwymi
    vpwxmi = ( vpwxmax > vpwxmin ) ? vpwxmin : vpwxmax;
    vpwxma = ( vpwxmax > vpwxmin ) ? vpwxmax : vpwxmin;
    vpwymi = ( vpwymax > vpwymin ) ? vpwymin : vpwymax;
    vpwyma = ( vpwymax > vpwymin ) ? vpwymax : vpwymin;

// Draw grid in x direction.

    if ( lgx )
    {
        if ( ldx )
        {
            pldtfac( vpwxmi, vpwxma, &factor, &tstart );
            tn = xtick1 * ( floor( ( vpwxmi - tstart ) / xtick1 ) ) + tstart;
        }
        else
        {
            tn = xtick1 * floor( vpwxmi / xtick1 );
        }
        for (; tn <= vpwxma; tn += xtick1 )
        {
            if ( lhx )
            {
                if ( llx )
                {
                    PLFLT otemp = tn;
                    for ( i = 0; i <= 7; i++ )
                    {
                        temp  = tn + xlog[i];
                        tcrit = ( temp - otemp ) * tspace;
                        otemp = temp;
                        if ( BETW( temp, vpwxmi + tcrit, vpwxma - tcrit ) )
                            pljoin( temp, vpwymi, temp, vpwyma );
                    }
                }
                else
                {
                    for ( i = 1; i <= nxsub1 - 1; i++ )
                    {
                        temp  = tn + i * xtick1 / nxsub1;
                        tcrit = xtick1 / nxsub1 * tspace;
                        if ( BETW( temp, vpwxmi + tcrit, vpwxma - tcrit ) )
                            pljoin( temp, vpwymi, temp, vpwyma );
                    }
                }
            }
            tcrit = xtick1 * tspace;
            if ( BETW( tn, vpwxmi + tcrit, vpwxma - tcrit ) )
                pljoin( tn, vpwymi, tn, vpwyma );
        }
    }

// Draw grid in y direction

    if ( lgy )
    {
        if ( ldy )
        {
            pldtfac( vpwymi, vpwyma, &factor, &tstart );
            tn = ytick1 * ( floor( ( vpwymi - tstart ) / ytick1 ) ) + tstart;
        }
        else
        {
            tn = ytick1 * floor( vpwymi / ytick1 );
        }
        for (; tn <= vpwyma; tn += ytick1 )
        {
            if ( lhy )
            {
                if ( lly )
                {
                    PLFLT otemp = tn;
                    for ( i = 0; i <= 7; i++ )
                    {
                        temp  = tn + xlog[i];
                        tcrit = ( temp - otemp ) * tspace;
                        otemp = temp;
                        if ( BETW( temp, vpwymi + tcrit, vpwyma - tcrit ) )
                            pljoin( vpwxmi, temp, vpwxma, temp );
                    }
                }
                else
                {
                    for ( i = 1; i <= nysub1 - 1; i++ )
                    {
                        temp  = tn + i * ytick1 / nysub1;
                        tcrit = ytick1 / nysub1 * tspace;
                        if ( BETW( temp, vpwymi + tcrit, vpwyma - tcrit ) )
                            pljoin( vpwxmi, temp, vpwxma, temp );
                    }
                }
            }
            tcrit = ytick1 * tspace;
            if ( BETW( tn, vpwymi + tcrit, vpwyma - tcrit ) )
                pljoin( vpwxmi, tn, vpwxma, tn );
        }
    }
}

//--------------------------------------------------------------------------
// void label_box()
//
// Writes numeric labels on side(s) of box.
//--------------------------------------------------------------------------

static void
label_box( const char *xopt, PLFLT xtick1, const char *yopt, PLFLT ytick1 )
{
    static char string[STRING_LEN];
    PLBOOL      ldx, lfx, lix, llx, lmx, lnx, ltx, lox, lxx;
    PLBOOL      ldy, lfy, liy, lly, lmy, lny, lty, lvy, loy, lxy;
    PLFLT       vpwxmi, vpwxma, vpwymi, vpwyma;
    PLFLT       vpwxmin, vpwxmax, vpwymin, vpwymax;
    PLFLT       tn, tp, offset;
    PLFLT       factor, tstart;
    const char  *timefmt = NULL;
    PLFLT       default_mm, char_height_mm, height_mm;
    PLFLT       string_length_mm = 0.0, pos_mm = 0.0;

    // Assume label data is for placement of exponents if no custom
    // label function is provided.
    PLBOOL custom_exponent_placement = !plsc->label_func && plsc->label_data;

    // pos, height, and just are unnecessarily set to quiet
    // -O3 -Wuninitialized warnings that are obvious false alarms from
    // the clarity of the code associated with the true or false
    // result for custom_exponent_placement.
    PLFLT      pos          = 0.0, height = 0.0, just = 0.0;
    const char * esc_string = plgesc_string();

    plgchr( &default_mm, &char_height_mm );

// Set plot options from input

    ldx = plP_stsearch( xopt, 'd' );
    lfx = plP_stsearch( xopt, 'f' );
    lix = plP_stsearch( xopt, 'i' );
    llx = plP_stsearch( xopt, 'l' );
    lmx = plP_stsearch( xopt, 'm' );
    lnx = plP_stsearch( xopt, 'n' );
    ltx = plP_stsearch( xopt, 't' );
    lox = plP_stsearch( xopt, 'o' );
    lxx = plP_stsearch( xopt, 'x' );

    ldy = plP_stsearch( yopt, 'd' );
    lfy = plP_stsearch( yopt, 'f' );
    liy = plP_stsearch( yopt, 'i' );
    lly = plP_stsearch( yopt, 'l' );
    lmy = plP_stsearch( yopt, 'm' );
    lny = plP_stsearch( yopt, 'n' );
    lty = plP_stsearch( yopt, 't' );
    lvy = plP_stsearch( yopt, 'v' );
    loy = plP_stsearch( yopt, 'o' );
    lxy = plP_stsearch( yopt, 'x' );

    plP_xgvpw( &vpwxmin, &vpwxmax, &vpwymin, &vpwymax );
// vpwxmi always numerically less than vpwxma, and
// similarly for vpwymi
    vpwxmi = ( vpwxmax > vpwxmin ) ? vpwxmin : vpwxmax;
    vpwxma = ( vpwxmax > vpwxmin ) ? vpwxmax : vpwxmin;
    vpwymi = ( vpwymax > vpwymin ) ? vpwymin : vpwymax;
    vpwyma = ( vpwymax > vpwymin ) ? vpwymax : vpwymin;

    // Write label(s) for horizontal axes.
    if ( ( lmx || lnx ) && ( ltx || lxx ) )
    {
        PLINT xmode, xprec, xdigmax, xdigits, xscale;

        plgxax( &xdigmax, &xdigits );
        pldprec( vpwxmi, vpwxma, xtick1, lfx, &xmode, &xprec, xdigmax, &xscale );
        timefmt = plP_gtimefmt();

        if ( ldx )
        {
            pldtfac( vpwxmi, vpwxma, &factor, &tstart );
            tp = xtick1 * ( 1. + floor( ( vpwxmi - tstart ) / xtick1 ) ) + tstart;
        }
        else
        {
            tp = xtick1 * ( 1. + floor( vpwxmi / xtick1 ) );
        }
        height = lix ? 1.75 : 1.5;
        if ( plsc->if_boxbb )
        {
            // For horizontal axes, height of zero corresponds to
            // character centred on edge so should add 0.5 to height
            // to obtain bounding box edge in direction away from
            // edge.  However, experimentally found 0.7 gave a better
            // looking result.
            height_mm = ( height + 0.7 ) * char_height_mm;
            if ( lnx )
                plsc->boxbb_ymin = MIN( plsc->boxbb_ymin, plsc->vppymi /
                    plsc->ypmm - height_mm );
            if ( lmx )
                plsc->boxbb_ymax = MAX( plsc->boxbb_ymax, plsc->vppyma /
                    plsc->ypmm + height_mm );
        }

        for ( tn = tp; BETW( tn, vpwxmi, vpwxma ); tn += xtick1 )
        {
            if ( ldx )
            {
                strfqsas( string, STRING_LEN, timefmt, (double) tn, plsc->qsasconfig );
            }
            else
            {
                plform( PL_X_AXIS, tn, xscale, xprec, string, STRING_LEN, llx, lfx, lox );
            }
            pos = ( vpwxmax > vpwxmin ) ?
                  ( tn - vpwxmi ) / ( vpwxma - vpwxmi ) :
                  ( vpwxma - tn ) / ( vpwxma - vpwxmi );
            if ( plsc->if_boxbb )
            {
                string_length_mm = plstrl( string );
                pos_mm           = ( plsc->vppxmi + pos *
                                     ( plsc->vppxma - plsc->vppxmi ) ) /
                                   plsc->xpmm;
            }
            if ( lnx )
            {
                // Bottom axis.
                if ( plsc->if_boxbb )
                {
                    plsc->boxbb_xmin = MIN( plsc->boxbb_xmin,
                        pos_mm - 0.5 * string_length_mm );
                    plsc->boxbb_xmax = MAX( plsc->boxbb_xmax,
                        pos_mm + 0.5 * string_length_mm );
                }
                else
                {
                    plmtex( "b", height, pos, 0.5, string );
                }
            }
            if ( lmx )
            {
                // Top axis.
                if ( plsc->if_boxbb )
                {
                    plsc->boxbb_xmin = MIN( plsc->boxbb_xmin,
                        pos_mm - 0.5 * string_length_mm );
                    plsc->boxbb_xmax = MAX( plsc->boxbb_xmax,
                        pos_mm + 0.5 * string_length_mm );
                }
                else
                {
                    plmtex( "t", height, pos, 0.5, string );
                }
            }
        }
        xdigits = 2;
        plsxax( xdigmax, xdigits );

        // Write separate exponential label if mode = 1.

        if ( !llx && !ldx && !lox && xmode )
        {
            if ( custom_exponent_placement )
            {
                height = ( (PLLabelDefaults *) plsc->label_data )->exp_label_disp;
                pos    = ( (PLLabelDefaults *) plsc->label_data )->exp_label_pos;
                just   = ( (PLLabelDefaults *) plsc->label_data )->exp_label_just;
            }
            else
            {
                height = 3.2;
                pos    = 1.0;
                just   = 0.5;
            }
            snprintf( string, STRING_LEN, "(x10%su%d%sd)", esc_string, (int) xscale, esc_string );
            if ( lnx )
            {
                // Bottom axis exponent.
                if ( plsc->if_boxbb )
                {
                    // For horizontal axes, height of zero corresponds
                    // to character centred on edge so should add 0.5
                    // to height to obtain bounding box edge in
                    // direction away from edge if no exponent.  Add
                    // an additional offset to make exponent fit.
                    height_mm        = ( height + 0.9 ) * char_height_mm;
                    plsc->boxbb_ymin = MIN( plsc->boxbb_ymin, plsc->vppymi /
                        plsc->ypmm - height_mm );
                    string_length_mm = plstrl( string );
                    pos_mm           = ( plsc->vppxmi + pos *
                                         ( plsc->vppxma - plsc->vppxmi ) ) /
                                       plsc->xpmm;
                    plsc->boxbb_xmin = MIN( plsc->boxbb_xmin,
                        pos_mm - 0.5 * string_length_mm );
                    plsc->boxbb_xmax = MAX( plsc->boxbb_xmax,
                        pos_mm + 0.5 * string_length_mm );
                }
                else
                {
                    plmtex( "b", height, pos, just, string );
                }
            }
            if ( lmx )
            {
                // Top axis exponent.
                if ( plsc->if_boxbb )
                {
                    // For horizontal axes, height of zero corresponds
                    // to character centred on edge so should add 0.5
                    // to height to obtain bounding box edge in
                    // direction away from edge if no exponent.  Add
                    // an additional offset to make exponent fit.
                    height_mm        = ( height + 1.4 ) * char_height_mm;
                    plsc->boxbb_ymax = MAX( plsc->boxbb_ymax, plsc->vppyma /
                        plsc->ypmm + height_mm );
                    string_length_mm = plstrl( string );
                    pos_mm           = ( plsc->vppxmi + pos *
                                         ( plsc->vppxma - plsc->vppxmi ) ) /
                                       plsc->xpmm;
                    plsc->boxbb_xmin = MIN( plsc->boxbb_xmin,
                        pos_mm - 0.5 * string_length_mm );
                    plsc->boxbb_xmax = MAX( plsc->boxbb_xmax,
                        pos_mm + 0.5 * string_length_mm );
                }
                else
                {
                    plmtex( "t", height, pos, just, string );
                }
            }
        }
    }

    // Write label(s) for vertical axes.

    if ( ( lmy || lny ) && ( lty || lxy ) )
    {
        PLINT ymode, yprec, ydigmax, ydigits, yscale;

        plgyax( &ydigmax, &ydigits );
        pldprec( vpwymi, vpwyma, ytick1, lfy, &ymode, &yprec, ydigmax, &yscale );

        ydigits = 0;
        if ( ldy )
        {
            pldtfac( vpwymi, vpwyma, &factor, &tstart );
            tp = ytick1 * ( 1. + floor( ( vpwymi - tstart ) / ytick1 ) ) + tstart;
        }
        else
        {
            tp = ytick1 * ( 1. + floor( vpwymi / ytick1 ) );
        }
        for ( tn = tp; BETW( tn, vpwymi, vpwyma ); tn += ytick1 )
        {
            if ( ldy )
            {
                strfqsas( string, STRING_LEN, timefmt, (double) tn, plsc->qsasconfig );
            }
            else
            {
                plform( PL_Y_AXIS, tn, yscale, yprec, string, STRING_LEN, lly, lfy, loy );
            }
            pos = ( vpwymax > vpwymin ) ?
                  ( tn - vpwymi ) / ( vpwyma - vpwymi ) :
                  ( vpwyma - tn ) / ( vpwyma - vpwymi );
            if ( lny )
            {
                if ( lvy )
                {
                    // Left axis with text written perpendicular to edge.
                    height = liy ? 1.0 : 0.5;
                    if ( plsc->if_boxbb )
                    {
                        // For vertical axes with text written
                        // perpendicular to edge, height of zero
                        // corresponds character centred on edge so
                        // should add 0.5 to height to obtain bounding
                        // box edge in direction away from edge, and
                        // that value apparently works.
                        height_mm        = ( height + 0.0 ) * char_height_mm;
                        string_length_mm = plstrl( string );
                        plsc->boxbb_xmin = MIN( plsc->boxbb_xmin, plsc->vppxmi /
                            plsc->xpmm - height_mm - string_length_mm );
                        pos_mm = ( plsc->vppymi + pos *
                                   ( plsc->vppyma - plsc->vppymi ) ) /
                                 plsc->ypmm;
                        // Expected offset is 0.5, but adjust to improve
                        // look of result.
                        plsc->boxbb_ymin = MIN( plsc->boxbb_ymin,
                            pos_mm - 0.6 * char_height_mm );
                        plsc->boxbb_ymax = MAX( plsc->boxbb_ymax,
                            pos_mm + 0.7 * char_height_mm );
                    }
                    else
                    {
                        plmtex( "lv", height, pos, 1.0, string );
                    }
                }
                else
                {
                    // Left axis with text written parallel to edge.
                    height = liy ? 1.75 : 1.5;
                    if ( plsc->if_boxbb )
                    {
                        // For vertical axes with text written
                        // parallel to edge, height of zero
                        // corresponds to character centred on edge so
                        // should add 0.5 to height to obtain bounding
                        // box edge in direction away from edge,
                        // However, experimentally found 0.8 gave a
                        // better looking result.
                        height_mm        = ( height + 0.8 ) * char_height_mm;
                        plsc->boxbb_xmin = MIN( plsc->boxbb_xmin, plsc->vppxmi /
                            plsc->xpmm - height_mm );
                        pos_mm = ( plsc->vppymi + pos *
                                   ( plsc->vppyma - plsc->vppymi ) ) /
                                 plsc->ypmm;
                        string_length_mm = plstrl( string );
                        plsc->boxbb_ymin = MIN( plsc->boxbb_ymin,
                            pos_mm - 0.5 * string_length_mm );
                        plsc->boxbb_ymax = MAX( plsc->boxbb_ymax,
                            pos_mm + 0.5 * string_length_mm );
                    }
                    else
                    {
                        plmtex( "l", height, pos, 0.5, string );
                    }
                }
            }
            if ( lmy )
            {
                if ( lvy )
                {
                    // Right axis with text written perpendicular to edge.
                    height = liy ? 1.0 : 0.5;
                    if ( plsc->if_boxbb )
                    {
                        // For vertical axes with text written
                        // perpendicular to edge, height of zero
                        // corresponds character centred on edge so
                        // should add 0.5 to height to obtain bounding
                        // box edge in direction away from edge, and
                        // that value apparently works.
                        height_mm        = ( height + 0.0 ) * char_height_mm;
                        string_length_mm = plstrl( string );
                        plsc->boxbb_xmax = MAX( plsc->boxbb_xmax, plsc->vppxma /
                            plsc->xpmm + height_mm + string_length_mm );
                        pos_mm = ( plsc->vppymi + pos *
                                   ( plsc->vppyma - plsc->vppymi ) ) /
                                 plsc->ypmm;
                        // Expected offset is 0.5, but adjust to improve
                        // look of result.
                        plsc->boxbb_ymin = MIN( plsc->boxbb_ymin,
                            pos_mm - 0.6 * char_height_mm );
                        plsc->boxbb_ymax = MAX( plsc->boxbb_ymax,
                            pos_mm + 0.7 * char_height_mm );
                    }
                    else
                    {
                        plmtex( "rv", height, pos, 0.0, string );
                    }
                }
                else
                {
                    // Right axis with text written parallel to edge.
                    height = liy ? 1.75 : 1.5;
                    if ( plsc->if_boxbb )
                    {
                        // For vertical axes with text written
                        // parallel to edge, height of zero
                        // corresponds to character centred on edge so
                        // should add 0.5 to height to obtain bounding
                        // box edge in direction away from edge,
                        // However, experimentally found 0.8 gave a
                        // better looking result.
                        height_mm        = ( height + 0.8 ) * char_height_mm;
                        plsc->boxbb_xmax = MAX( plsc->boxbb_xmax, plsc->vppxma /
                            plsc->xpmm + height_mm );
                        pos_mm = ( plsc->vppymi + pos *
                                   ( plsc->vppyma - plsc->vppymi ) ) /
                                 plsc->ypmm;
                        string_length_mm = plstrl( string );
                        plsc->boxbb_ymin = MIN( plsc->boxbb_ymin,
                            pos_mm - 0.5 * string_length_mm );
                        plsc->boxbb_ymax = MAX( plsc->boxbb_ymax,
                            pos_mm + 0.5 * string_length_mm );
                    }
                    else
                    {
                        plmtex( "r", height, pos, 0.5, string );
                    }
                }
            }
            ydigits = MAX( ydigits, (PLINT) strlen( string ) );
        }
        if ( !lvy )
            ydigits = 2;

        plsyax( ydigmax, ydigits );

        // Write separate exponential label if mode = 1.

        if ( !lly && !ldy && !loy && ymode )
        {
            snprintf( string, STRING_LEN, "(x10%su%d%sd)", esc_string, (int) yscale, esc_string );
            if ( custom_exponent_placement )
            {
                height = ( (PLLabelDefaults *) plsc->label_data )->exp_label_disp;
                pos    = ( (PLLabelDefaults *) plsc->label_data )->exp_label_pos;
                just   = ( (PLLabelDefaults *) plsc->label_data )->exp_label_just;
            }
            if ( lvy )
            {
                offset = 0.1;  // more space to clear labels in "v" mode
            }
            else
            {
                offset = 0.02;
            }
            // Left axis exponent
            if ( lny )
            {
                if ( !custom_exponent_placement )
                {
                    height = 3.2;
                    pos    = 1.0 + offset;
                    just   = 0.5;
                }
                if ( plsc->if_boxbb )
                {
                    // For horizontal axes, height of zero corresponds
                    // to character centred on edge so should add 0.5
                    // to height to obtain bounding box edge in
                    // direction away from edge if no exponent.  Add
                    // an additional offset to make exponent fit.
                    height_mm        = ( height + 1.4 ) * char_height_mm;
                    plsc->boxbb_ymax = MAX( plsc->boxbb_ymax, plsc->vppyma /
                        plsc->ypmm + height_mm );
                    string_length_mm = plstrl( string );
                    pos_mm           = ( plsc->vppxmi + pos *
                                         ( plsc->vppxma - plsc->vppxmi ) ) /
                                       plsc->xpmm;
                    plsc->boxbb_xmin = MIN( plsc->boxbb_xmin,
                        pos_mm - string_length_mm );
                }
                else
                {
                    if ( lvy )
                    {
                        plmtex( "lv", height, pos, just, string );
                    }
                    else
                    {
                        plmtex( "l", height, pos, just, string );
                    }
                }
            }
            // Right axis exponent.
            if ( lmy )
            {
                if ( !custom_exponent_placement )
                {
                    height = 3.4;   // Extra space for superscript
                    pos    = 1.0 + offset;
                    just   = 0.5;
                }
                if ( plsc->if_boxbb )
                {
                    // For horizontal axes, height of zero corresponds
                    // to character centred on edge so should add 0.5
                    // to height to obtain bounding box edge in
                    // direction away from edge if no exponent.  Add
                    // an additional offset to make exponent fit.
                    height_mm        = ( height + 1.4 ) * char_height_mm;
                    plsc->boxbb_ymax = MAX( plsc->boxbb_ymax, plsc->vppyma /
                        plsc->ypmm + height_mm );
                    string_length_mm = plstrl( string );
                    pos_mm           = ( plsc->vppxmi + pos *
                                         ( plsc->vppxma - plsc->vppxmi ) ) /
                                       plsc->xpmm;
                    plsc->boxbb_xmax = MAX( plsc->boxbb_xmin,
                        pos_mm + string_length_mm );
                }
                else
                {
                    if ( lvy )
                    {
                        plmtex( "rv", height, pos, just, string );
                    }
                    else
                    {
                        plmtex( "r", height, pos, just, string );
                    }
                }
            }
        }
    }
}

//--------------------------------------------------------------------------
// void label_box_custom()
//
// Writes numeric labels on side(s) of box in custom locations
//--------------------------------------------------------------------------

void
label_box_custom( const char *xopt, PLINT n_xticks, const PLFLT *xticks, const char *yopt, PLINT n_yticks, const PLFLT *yticks )
{
    static char string[STRING_LEN];
    PLBOOL      ldx, lfx, lix, llx, lmx, lnx, ltx, lox, lxx;
    PLBOOL      ldy, lfy, liy, lly, lmy, lny, lty, lvy, loy, lxy;
    PLFLT       vpwxmi, vpwxma, vpwymi, vpwyma;
    PLFLT       vpwxmin, vpwxmax, vpwymin, vpwymax;
    PLFLT       tn, offset;
    const char  *timefmt;
    PLINT       i;
    PLINT       xdigmax, xdigits, xdigmax_old, xdigits_old;
    PLINT       ydigmax, ydigits, ydigmax_old, ydigits_old;
    PLINT       lxmin, lxmax, lymin, lymax;
    PLINT       pxmin, pxmax, pymin, pymax;
    PLFLT       default_mm, char_height_mm, height_mm;
    PLFLT       string_length_mm = 0.0, pos_mm = 0.0;

    // Assume label data is for placement of exponents if no custom
    // label function is provided.
    PLBOOL custom_exponent_placement = !plsc->label_func && plsc->label_data;

    // pos, height, and just are unnecessarily set to quiet
    // -O3 -Wuninitialized warnings that are obvious false alarms from
    // the clarity of the code associated with the true or false
    // result for custom_exponent_placement.
    PLFLT      pos          = 0.0, height = 0.0, just = 0.0;
    const char * esc_string = plgesc_string();

    plgchr( &default_mm, &char_height_mm );

    // Save some parameters
    plgxax( &xdigmax, &xdigits );
    plgyax( &ydigmax, &ydigits );
    xdigmax_old = xdigmax;
    xdigits_old = xdigits;
    ydigmax_old = ydigmax;
    ydigits_old = ydigits;

// Open the clip limits to the subpage limits

    plP_gclp( &lxmin, &lxmax, &lymin, &lymax );
    plP_gphy( &pxmin, &pxmax, &pymin, &pymax );
    plP_sclp( pxmin, pxmax, pymin, pymax );

// Set plot options from input

    ldx = plP_stsearch( xopt, 'd' );
    lfx = plP_stsearch( xopt, 'f' );
    lix = plP_stsearch( xopt, 'i' );
    llx = plP_stsearch( xopt, 'l' );
    lmx = plP_stsearch( xopt, 'm' );
    lnx = plP_stsearch( xopt, 'n' );
    ltx = plP_stsearch( xopt, 't' );
    lox = plP_stsearch( xopt, 'o' );
    lxx = plP_stsearch( xopt, 'x' );

    ldy = plP_stsearch( yopt, 'd' );
    lfy = plP_stsearch( yopt, 'f' );
    liy = plP_stsearch( yopt, 'i' );
    lly = plP_stsearch( yopt, 'l' );
    lmy = plP_stsearch( yopt, 'm' );
    lny = plP_stsearch( yopt, 'n' );
    lty = plP_stsearch( yopt, 't' );
    lvy = plP_stsearch( yopt, 'v' );
    loy = plP_stsearch( yopt, 'o' );
    lxy = plP_stsearch( yopt, 'x' );

    plP_xgvpw( &vpwxmin, &vpwxmax, &vpwymin, &vpwymax );
// n.b. large change; vpwxmi always numerically less than vpwxma, and
// similarly for vpwymi
    vpwxmi = ( vpwxmax > vpwxmin ) ? vpwxmin : vpwxmax;
    vpwxma = ( vpwxmax > vpwxmin ) ? vpwxmax : vpwxmin;
    vpwymi = ( vpwymax > vpwymin ) ? vpwymin : vpwymax;
    vpwyma = ( vpwymax > vpwymin ) ? vpwymax : vpwymin;

    if ( plsc->if_boxbb )
    {
        PLINT xmajor = MAX( ROUND( plsc->majht * plsc->ypmm ), 1 );
        PLINT ymajor = MAX( ROUND( plsc->majht * plsc->xpmm ), 1 );
        // Bounding-box limits for the box in mm before corrections
        // for decorations are applied.
        plsc->boxbb_xmin = plsc->vppxmi / plsc->xpmm;
        plsc->boxbb_xmax = plsc->vppxma / plsc->xpmm;
        plsc->boxbb_ymin = plsc->vppymi / plsc->ypmm;
        plsc->boxbb_ymax = plsc->vppyma / plsc->ypmm;
        // Carefully follow logic below for the case where
        // an inverted major tick mark is written (in the X direction
        // for a Y axis and vice versa).  Ignore minor tick marks
        // which are assumed to be smaller.
        if ( lix && ( lmx || lnx ) && ( ltx && !lxx ) )
        {
            plsc->boxbb_ymin -= xmajor / plsc->ypmm;
            plsc->boxbb_ymax += xmajor / plsc->ypmm;
        }
        if ( liy && ( lmy || lny ) && ( lty && !lxy ) )
        {
            plsc->boxbb_xmin -= ymajor / plsc->xpmm;
            plsc->boxbb_xmax += ymajor / plsc->xpmm;
        }
    }
    // Write label(s) for horizontal axes.

    if ( ( lmx || lnx ) && ( ltx || lxx ) )
    {
        PLINT xmode, xprec, xscale;
        PLFLT x_spacing, x_spacing_tmp;

        // Determine spacing between ticks
        // Use the x-size of the window
        x_spacing = vpwxma - vpwxmi;
        if ( n_xticks > 1 )
        {
            // Use the smallest space between ticks
            for ( i = 1; i < n_xticks; i++ )
            {
                x_spacing_tmp = fabs( xticks[i] - xticks[i - 1] );
                x_spacing     = MIN( x_spacing, x_spacing_tmp );
            }
        }

        plgxax( &xdigmax, &xdigits );
        pldprec( vpwxmi, vpwxma, x_spacing, lfx, &xmode, &xprec, xdigmax, &xscale );
        timefmt = plP_gtimefmt();

        height = lix ? 1.75 : 1.5;
        if ( plsc->if_boxbb )
        {
            // For horizontal axes, height of zero corresponds to
            // character centred on edge so should add 0.5 to height
            // to obtain bounding box edge in direction away from
            // edge.  However, experimentally found 0.7 gave a better
            // looking result.
            height_mm = ( height + 0.7 ) * char_height_mm;
            if ( lnx )
                plsc->boxbb_ymin = MIN( plsc->boxbb_ymin, plsc->vppymi /
                    plsc->ypmm - height_mm );
            if ( lmx )
                plsc->boxbb_ymax = MAX( plsc->boxbb_ymax, plsc->vppyma /
                    plsc->ypmm + height_mm );
        }
        // Loop through all of the tick marks
        for ( i = 0; i < n_xticks; i++ )
        {
            tn = xticks[i];
            if ( BETW( tn, vpwxmi, vpwxma ) )
            {
                if ( !lxx && !plsc->if_boxbb )
                {
                    plwxtik( tn, vpwymin, FALSE, !lix );
                    plwxtik( tn, vpwymax, FALSE, lix );
                }
                if ( ldx )
                {
                    strfqsas( string, STRING_LEN, timefmt, (double) tn, plsc->qsasconfig );
                }
                else
                {
                    plform( PL_X_AXIS, tn, xscale, xprec, string, STRING_LEN, llx, lfx, lox );
                }
                pos = ( vpwxmax > vpwxmin ) ?
                      ( tn - vpwxmi ) / ( vpwxma - vpwxmi ) :
                      ( vpwxma - tn ) / ( vpwxma - vpwxmi );
                if ( plsc->if_boxbb )
                {
                    string_length_mm = plstrl( string );
                    pos_mm           = ( plsc->vppxmi + pos *
                                         ( plsc->vppxma - plsc->vppxmi ) ) /
                                       plsc->xpmm;
                }
                if ( lnx )
                {
                    // Bottom axis.
                    if ( plsc->if_boxbb )
                    {
                        plsc->boxbb_xmin = MIN( plsc->boxbb_xmin,
                            pos_mm - 0.5 * string_length_mm );
                        plsc->boxbb_xmax = MAX( plsc->boxbb_xmax,
                            pos_mm + 0.5 * string_length_mm );
                    }
                    else
                    {
                        plmtex( "b", height, pos, 0.5, string );
                    }
                }
                if ( lmx )
                {
                    // Top axis.
                    if ( plsc->if_boxbb )
                    {
                        plsc->boxbb_xmin = MIN( plsc->boxbb_xmin,
                            pos_mm - 0.5 * string_length_mm );
                        plsc->boxbb_xmax = MAX( plsc->boxbb_xmax,
                            pos_mm + 0.5 * string_length_mm );
                    }
                    else
                    {
                        plmtex( "t", height, pos, 0.5, string );
                    }
                }
            }
        }
        xdigits = 2;
        plsxax( xdigmax, xdigits );

        // Write separate exponential label if mode = 1.

        if ( !llx && !ldx && !lox && xmode )
        {
            if ( custom_exponent_placement )
            {
                height = ( (PLLabelDefaults *) plsc->label_data )->exp_label_disp;
                pos    = ( (PLLabelDefaults *) plsc->label_data )->exp_label_pos;
                just   = ( (PLLabelDefaults *) plsc->label_data )->exp_label_just;
            }
            else
            {
                height = 3.2;
                pos    = 1.0;
                just   = 0.5;
            }
            snprintf( string, STRING_LEN, "(x10%su%d%sd)", esc_string, (int) xscale, esc_string );
            if ( lnx )
            {
                // Bottom axis exponent.
                if ( plsc->if_boxbb )
                {
                    // For horizontal axes, height of zero corresponds
                    // to character centred on edge so should add 0.5
                    // to height to obtain bounding box edge in
                    // direction away from edge if no exponent.  Add
                    // an additional offset to make exponent fit.
                    height_mm        = ( height + 0.9 ) * char_height_mm;
                    plsc->boxbb_ymin = MIN( plsc->boxbb_ymin, plsc->vppymi /
                        plsc->ypmm - height_mm );
                    string_length_mm = plstrl( string );
                    pos_mm           = ( plsc->vppxmi + pos *
                                         ( plsc->vppxma - plsc->vppxmi ) ) /
                                       plsc->xpmm;
                    plsc->boxbb_xmin = MIN( plsc->boxbb_xmin,
                        pos_mm - 0.5 * string_length_mm );
                    plsc->boxbb_xmax = MAX( plsc->boxbb_xmax,
                        pos_mm + 0.5 * string_length_mm );
                }
                else
                {
                    plmtex( "b", height, pos, just, string );
                }
            }
            if ( lmx )
            {
                // Top axis exponent.
                if ( plsc->if_boxbb )
                {
                    // For horizontal axes, height of zero corresponds
                    // to character centred on edge so should add 0.5
                    // to height to obtain bounding box edge in
                    // direction away from edge if no exponent.  Add
                    // an additional offset to make exponent fit.
                    height_mm        = ( height + 1.4 ) * char_height_mm;
                    plsc->boxbb_ymax = MAX( plsc->boxbb_ymax, plsc->vppyma /
                        plsc->ypmm + height_mm );
                    string_length_mm = plstrl( string );
                    pos_mm           = ( plsc->vppxmi + pos *
                                         ( plsc->vppxma - plsc->vppxmi ) ) /
                                       plsc->xpmm;
                    plsc->boxbb_xmin = MIN( plsc->boxbb_xmin,
                        pos_mm - 0.5 * string_length_mm );
                    plsc->boxbb_xmax = MAX( plsc->boxbb_xmax,
                        pos_mm + 0.5 * string_length_mm );
                }
                else
                {
                    plmtex( "t", height, pos, just, string );
                }
            }
        }
    }

    // Write label(s) for vertical axes.
    if ( ( lmy || lny ) && ( lty || lxy ) )
    {
        PLINT ymode, yprec, yscale;
        PLFLT y_spacing, y_spacing_tmp;

        // Determine spacing between ticks
        // Use the y-size of the window
        y_spacing = vpwyma - vpwymi;
        if ( n_yticks > 1 )
        {
            // Use the smallest space between ticks
            for ( i = 1; i < n_yticks; i++ )
            {
                y_spacing_tmp = fabs( yticks[i] - yticks[i - 1] );
                y_spacing     = MIN( y_spacing, y_spacing_tmp );
            }
        }

        plgyax( &ydigmax, &ydigits );
        pldprec( vpwymi, vpwyma, y_spacing, lfy, &ymode, &yprec, ydigmax, &yscale );
        timefmt = plP_gtimefmt();

        ydigits = 0;
        for ( i = 0; i < n_yticks; i++ )
        {
            tn = yticks[i];
            if ( BETW( tn, vpwymi, vpwyma ) )
            {
                if ( !lxy && !plsc->if_boxbb )
                {
                    plwytik( vpwxmin, tn, FALSE, !liy );
                    plwytik( vpwxmax, tn, FALSE, liy );
                }
                if ( ldy )
                {
                    strfqsas( string, STRING_LEN, timefmt, (double) tn, plsc->qsasconfig );
                }
                else
                {
                    plform( PL_Y_AXIS, tn, yscale, yprec, string, STRING_LEN, lly, lfy, loy );
                }
                pos = ( vpwymax > vpwymin ) ?
                      ( tn - vpwymi ) / ( vpwyma - vpwymi ) :
                      ( vpwyma - tn ) / ( vpwyma - vpwymi );
                if ( lny )
                {
                    if ( lvy )
                    {
                        // Left axis with text written perpendicular to edge.
                        height = liy ? 1.0 : 0.5;
                        if ( plsc->if_boxbb )
                        {
                            // For vertical axes with text written
                            // perpendicular to edge, height of zero
                            // corresponds character centred on edge so
                            // should add 0.5 to height to obtain bounding
                            // box edge in direction away from edge, and
                            // that value apparently works.
                            height_mm        = ( height + 0.0 ) * char_height_mm;
                            string_length_mm = plstrl( string );
                            plsc->boxbb_xmin = MIN( plsc->boxbb_xmin, plsc->vppxmi /
                                plsc->xpmm - height_mm - string_length_mm );
                            pos_mm = ( plsc->vppymi + pos *
                                       ( plsc->vppyma - plsc->vppymi ) ) /
                                     plsc->ypmm;
                            // Expected offset is 0.5, but adjust to improve
                            // look of result.
                            plsc->boxbb_ymin = MIN( plsc->boxbb_ymin,
                                pos_mm - 0.6 * char_height_mm );
                            plsc->boxbb_ymax = MAX( plsc->boxbb_ymax,
                                pos_mm + 0.7 * char_height_mm );
                        }
                        else
                        {
                            plmtex( "lv", height, pos, 1.0, string );
                        }
                    }
                    else
                    {
                        // Left axis with text written parallel to edge.
                        height = liy ? 1.75 : 1.5;
                        if ( plsc->if_boxbb )
                        {
                            // For vertical axes with text written
                            // parallel to edge, height of zero
                            // corresponds to character centred on edge so
                            // should add 0.5 to height to obtain bounding
                            // box edge in direction away from edge,
                            // However, experimentally found 0.8 gave a
                            // better looking result.
                            height_mm        = ( height + 0.8 ) * char_height_mm;
                            plsc->boxbb_xmin = MIN( plsc->boxbb_xmin, plsc->vppxmi /
                                plsc->xpmm - height_mm );
                            pos_mm = ( plsc->vppymi + pos *
                                       ( plsc->vppyma - plsc->vppymi ) ) /
                                     plsc->ypmm;
                            string_length_mm = plstrl( string );
                            plsc->boxbb_ymin = MIN( plsc->boxbb_ymin,
                                pos_mm - 0.5 * string_length_mm );
                            plsc->boxbb_ymax = MAX( plsc->boxbb_ymax,
                                pos_mm + 0.5 * string_length_mm );
                        }
                        else
                        {
                            plmtex( "l", height, pos, 0.5, string );
                        }
                    }
                }
                if ( lmy )
                {
                    if ( lvy )
                    {
                        // Right axis with text written perpendicular to edge.
                        height = liy ? 1.0 : 0.5;
                        if ( plsc->if_boxbb )
                        {
                            // For vertical axes with text written
                            // perpendicular to edge, height of zero
                            // corresponds character centred on edge so
                            // should add 0.5 to height to obtain bounding
                            // box edge in direction away from edge, and
                            // that value apparently works.
                            height_mm        = ( height + 0.0 ) * char_height_mm;
                            string_length_mm = plstrl( string );
                            plsc->boxbb_xmax = MAX( plsc->boxbb_xmax, plsc->vppxma /
                                plsc->xpmm + height_mm + string_length_mm );
                            pos_mm = ( plsc->vppymi + pos *
                                       ( plsc->vppyma - plsc->vppymi ) ) /
                                     plsc->ypmm;
                            // Expected offset is 0.5, but adjust to improve
                            // look of result.
                            plsc->boxbb_ymin = MIN( plsc->boxbb_ymin,
                                pos_mm - 0.6 * char_height_mm );
                            plsc->boxbb_ymax = MAX( plsc->boxbb_ymax,
                                pos_mm + 0.7 * char_height_mm );
                        }
                        else
                        {
                            plmtex( "rv", height, pos, 0.0, string );
                        }
                    }
                    else
                    {
                        // Right axis with text written parallel to edge.
                        height = liy ? 1.75 : 1.5;
                        if ( plsc->if_boxbb )
                        {
                            // For vertical axes with text written
                            // parallel to edge, height of zero
                            // corresponds to character centred on edge so
                            // should add 0.5 to height to obtain bounding
                            // box edge in direction away from edge,
                            // However, experimentally found 0.8 gave a
                            // better looking result.
                            height_mm        = ( height + 0.8 ) * char_height_mm;
                            plsc->boxbb_xmax = MAX( plsc->boxbb_xmax, plsc->vppxma /
                                plsc->xpmm + height_mm );
                            pos_mm = ( plsc->vppymi + pos *
                                       ( plsc->vppyma - plsc->vppymi ) ) /
                                     plsc->ypmm;
                            string_length_mm = plstrl( string );
                            plsc->boxbb_ymin = MIN( plsc->boxbb_ymin,
                                pos_mm - 0.5 * string_length_mm );
                            plsc->boxbb_ymax = MAX( plsc->boxbb_ymax,
                                pos_mm + 0.5 * string_length_mm );
                        }
                        else
                        {
                            plmtex( "r", height, pos, 0.5, string );
                        }
                    }
                }
                ydigits = MAX( ydigits, (PLINT) strlen( string ) );
            }
        }
        if ( !lvy )
            ydigits = 2;

        plsyax( ydigmax, ydigits );

        // Write separate exponential label if mode = 1.

        if ( !lly && !ldy && !loy && ymode )
        {
            snprintf( string, STRING_LEN, "(x10%su%d%sd)", esc_string, (int) yscale, esc_string );
            if ( custom_exponent_placement )
            {
                height = ( (PLLabelDefaults *) plsc->label_data )->exp_label_disp;
                pos    = ( (PLLabelDefaults *) plsc->label_data )->exp_label_pos;
                just   = ( (PLLabelDefaults *) plsc->label_data )->exp_label_just;
            }
            if ( lvy )
            {
                offset = 0.1;  // more space to clear labels in "v" mode
            }
            else
            {
                offset = 0.02;
            }
            // Left axis exponent.
            if ( lny )
            {
                if ( !custom_exponent_placement )
                {
                    height = 3.2;
                    pos    = 1.0 + offset;
                    just   = 0.5;
                }
                if ( plsc->if_boxbb )
                {
                    // For horizontal axes, height of zero corresponds
                    // to character centred on edge so should add 0.5
                    // to height to obtain bounding box edge in
                    // direction away from edge if no exponent.  Add
                    // an additional offset to make exponent fit.
                    height_mm        = ( height + 1.4 ) * char_height_mm;
                    plsc->boxbb_ymax = MAX( plsc->boxbb_ymax, plsc->vppyma /
                        plsc->ypmm + height_mm );
                    string_length_mm = plstrl( string );
                    pos_mm           = ( plsc->vppxmi + pos *
                                         ( plsc->vppxma - plsc->vppxmi ) ) /
                                       plsc->xpmm;
                    plsc->boxbb_xmin = MIN( plsc->boxbb_xmin,
                        pos_mm - string_length_mm );
                }
                else
                {
                    if ( lvy )
                    {
                        plmtex( "lv", height, pos, just, string );
                    }
                    else
                    {
                        plmtex( "l", height, pos, just, string );
                    }
                }
            }
            // Right axis exponent.
            if ( lmy )
            {
                if ( !custom_exponent_placement )
                {
                    height = 3.4;   // Extra space for superscript
                    pos    = 1.0 + offset;
                    just   = 0.5;
                }
                if ( plsc->if_boxbb )
                {
                    // For horizontal axes, height of zero corresponds
                    // to character centred on edge so should add 0.5
                    // to height to obtain bounding box edge in
                    // direction away from edge if no exponent.  Add
                    // an additional offset to make exponent fit.
                    height_mm        = ( height + 1.4 ) * char_height_mm;
                    plsc->boxbb_ymax = MAX( plsc->boxbb_ymax, plsc->vppyma /
                        plsc->ypmm + height_mm );
                    string_length_mm = plstrl( string );
                    pos_mm           = ( plsc->vppxmi + pos *
                                         ( plsc->vppxma - plsc->vppxmi ) ) /
                                       plsc->xpmm;
                    plsc->boxbb_xmax = MAX( plsc->boxbb_xmin,
                        pos_mm + string_length_mm );
                }
                else
                {
                    if ( lvy )
                    {
                        plmtex( "rv", height, pos, just, string );
                    }
                    else
                    {
                        plmtex( "r", height, pos, just, string );
                    }
                }
            }
        }
    }

    // Restore saved parameters
    plsxax( xdigmax_old, xdigits_old );
    plsyax( ydigmax_old, ydigits_old );

    // Restore the clip limits to viewport edge
    plP_sclp( lxmin, lxmax, lymin, lymax );
}

//--------------------------------------------------------------------------
//
// Default labeling functions for PLplot
//
// These are the functions which are used internally by PLplot under various
// conditions.
//
// They have been separated out for use in other PLplot functions and
// potential exposure in the PLplot API.
//
//--------------------------------------------------------------------------
void plP_default_label_log( PLINT PL_UNUSED( axis ), PLFLT value, char *string, PLINT len, void * PL_UNUSED( data ) )
{
    const char * esc_string = plgesc_string();
    // Exponential, i.e. 10^-1, 10^0, 10^1, etc
    snprintf( string, (size_t) len, "10%su%d", esc_string, (int) ROUND( value ) );
}

void plP_default_label_log_fixed( PLINT PL_UNUSED( axis ), PLFLT value, char *string, PLINT len, void * PL_UNUSED( data ) )
{
    // Fixed point, i.e. .1, 1, 10, etc

    int exponent = ROUND( value );

    value = pow( 10.0, exponent );
    if ( exponent < 0 )
    {
        char form[FORMAT_LEN];
        snprintf( form, FORMAT_LEN, "%%.%df", ABS( exponent ) );
        snprintf( string, (size_t) len, form, value );
    }
    else
    {
        snprintf( string, (size_t) len, "%d", (int) value );
    }
}

void plP_default_label( PLINT PL_UNUSED( axis ), PLFLT value, char *string, PLINT len, void *data )
{
    PLINT  scale, prec;
    PLINT  setpre, precis;
    char   form[FORMAT_LEN], temp[TEMP_LEN];
    double scale2;

    scale = ( (PLINT *) data )[0];
    prec  = ( (PLINT *) data )[1];

    plP_gprec( &setpre, &precis );

    if ( setpre )
        prec = precis;

    if ( scale )
        value /= pow( 10., (double) scale );

    // This is necessary to prevent labels like "-0.0" on some systems

    scale2 = pow( 10., prec );
    value  = floor( ( value * scale2 ) + .5 ) / scale2;

    snprintf( form, FORMAT_LEN, "%%.%df", (int) prec );
    snprintf( temp, TEMP_LEN, form, value );
    strncpy( string, temp, (size_t) ( len - 1 ) );
    string[len - 1] = '\0';
}

//--------------------------------------------------------------------------
// void plform()
//
// Formats a PLFLT value in one of the following formats.
//
// If ll (logarithmic), then:
//
//    -	If lf (fixed), then used fixed point notation, i.e. .1, 1, 10, etc,
//	with unnecessary trailing .'s or 0's removed.
//
//    -	If !lf (default), then use exponential notation, i.e. 10^-1, etc.
//
// If !ll (linear), then:
//
//    - If scale == 0, use fixed point format with "prec" places after the
//	decimal point.
//
//    -	If scale == 1, use scientific notation with one place before the
//	decimal point and "prec" places after.  In this case, the value
//	must be divided by 10^scale.
//
// The axis argument is included to support PLplot's custom axis labeling.  It
// is passed on to the custom labeling function if it exists.  Otherwise, it
// is ignored.
//--------------------------------------------------------------------------

static void
plform( PLINT axis, PLFLT value, PLINT scale, PLINT prec, char *string, PLINT len, PLBOOL ll, PLBOOL lf, PLBOOL lo )
{
    // Check to see if a custom labeling function is defined.  If not,
    // use default.
    if ( lo && plsc->label_func )
    {
        ( *plsc->label_func )( axis, value, string, len, plsc->label_data );
    }
    else
    {
        if ( lo )
        {
            plwarn( "Custom axis labels requested without a labeling function \
                    - using default." );
        }
        if ( ll )
        {
            // Logarithmic

            if ( lf )
            {
                // Fixed point, i.e. .1, 1, 10, etc
                plP_default_label_log_fixed( axis, value, string, len, NULL );
            }
            else
            {
                // Exponential, i.e. 10^-1, 10^0, 10^1, etc
                plP_default_label_log( axis, value, string, len, NULL );
            }
        }
        else
        {
            // Linear
            PLINT scale_prec[2] = { scale, prec };
            plP_default_label( axis, value, string, len, (void *) scale_prec );
        }
    }
}

//--------------------------------------------------------------------------
// plslabelfunc
//
// Formats a PLFLT value in one of the following formats.
//
// label_func - A pointer to a function which will provide a string to use
//              as the label for the given floating point value.
//              Pass this as NULL to clear the custom function and reset it to
//              the default PLplot labeling function.
//
// label_data - Extra data to pass to the label function.
//
// The label_func function arguments are, in order:
//
// axis: PL_X_AXIS, PL_Y_AXIS or PL_Z_AXIS to indicate which axis is being
//       labeled
// value: The value at this position on the axis
// string: The resulting label string should be stored here
// data: A pointer to whatever extra information the custom plotting function
//       requires
//
//--------------------------------------------------------------------------
void
c_plslabelfunc( void ( *label_func )( PLINT, PLFLT, char *, PLINT, PLPointer ), PLPointer label_data )
{
    plsc->label_func = label_func;
    plsc->label_data = label_data;
}

static const char *
plgesc_string( void )
{
    static const char *esc_strings = { "!\0#\0$\0%\0&\0*\0@\0^\0~\0" };
    int d;
    // Follow plgesc logic here which is to set the default escape
    // if plsc->esc is in its initial state.
    if ( plsc->esc == '\0' )
        plsc->esc = '#';

    switch ( plsc->esc )
    {
    case '!':
        d = 0;
        break;
    case '#':
        d = 1;
        break;
    case '$':
        d = 2;
        break;
    case '%':
        d = 3;
        break;
    case '&':
        d = 4;
        break;
    case '*':
        d = 5;
        break;
    case '@':
        d = 6;
        break;
    case '^':
        d = 7;
        break;
    case '~':
        d = 8;
        break;
    default:
        plwarn( "plgesc_string: Invalid escape character, assuming '#' instead" );
        d = 1;
        break;
    }
    return &( esc_strings[d * 2] );
}
