/*
 * Grace - GRaphing, Advanced Computation and Exploration of data
 *
 * Home page: http://plasma-gate.weizmann.ac.il/Grace/
 *
 * Copyright (c) 1991-1995 Paul J Turner, Portland, OR
 * Copyright (c) 1996-2003 Grace Development Team
 *
 * Maintained by Evgeny Stambulchik
 *
 *
 *                           All Rights Reserved
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program 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 General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *
 * plotone.c - entry for graphics
 *
 */

#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <cmath.h>

#include "globals.h"
#include "utils.h"
#include "files.h"
#include "graphs.h"
#include "draw.h"
#include "device.h"
#include "plotone.h"
#include "protos.h"

#include "ssdata.h"

#include "ng_objects.h"
#include "zc.h"

extern void FillGTKRect (VPoint vp1 ,VPoint vp2);  //13 provisoire

extern GdkGC *gg_gc;
extern GdkColor  gg_colors[MAXCOLORS];

/*
 * draw all active graphs
 */
void drawgraph(void)
{
  int i ,layer;
    VPoint vp1, vp2;
    Pen pen;
    int saveg;

    saveg = get_cg();

    if (initgraphics() == RETURN_FAILURE) {
        errmsg("Device wasn't properly initialized");
        return;
    }

    setclipping(FALSE);

    if (getbgfill() == TRUE) {

        pen.color = getbgcolor();
        pen.pattern = 1;
        setpen(pen);

        vp1.x = 0.0;
        vp1.y = 0.0;
        get_page_viewport(&vp2.x, &vp2.y);

        FillRect(vp1, vp2);
    }

    reset_bboxes();
    activate_bbox(BBOX_TYPE_GLOB, TRUE);
    activate_bbox(BBOX_TYPE_TEMP, FALSE);

    /* Start with the deeper layer */
    for (layer = LAYER_MAX-1; layer > 0; layer--) {
      if (layer_not_used (layer)) continue;
      for (i = 0; i < number_of_graphs(); i++) {
        plotone 	   (i ,layer);
	draw_objs_in_graph (i ,layer);
      }
      /* draw objects NOT attached to a particular graph */
      draw_objs_in_graph  (-1 ,layer);       // draw QDobject not in graph
    }

    /* update bounding boxes of compounds */
    obj_update_compounds_bb ();

    if (get_cg() != saveg) {
        select_graph (saveg);
    }

    leavegraphics ();
}

void plotone (int gno ,int layer)
{
    GraphType gtype;

    if (is_graph_active(gno) != TRUE || is_graph_hidden(gno) == TRUE) {
        return;
    }

    setclipping(TRUE);

    set_draw_mode(TRUE);

    if (select_graph(gno) != RETURN_SUCCESS) {
        return;
    }

    /* fill frame */
    fillframe (gno ,layer);

    gtype = get_graph_type(gno);

    if (gtype != GRAPH_PIE) {
        /* calculate tick mark positions for all axes */
        calculate_tickgrid(gno);

        /* draw grid lines */
        drawgrid (gno ,layer);
    }

    /* plot type specific routines */
    switch(gtype) {
    case GRAPH_POLAR:
      draw_polar_graph (gno ,layer);
        break;
    case GRAPH_SMITH:
        draw_smith_chart (gno ,layer);
        break;
    case GRAPH_PIE:
        draw_pie_chart (gno ,layer);
        break;
    default:
      xyplot (gno ,layer);
        break;
    }

    if (gtype != GRAPH_PIE) {
        /* plot axes and tickmarks */
      drawaxes (gno ,layer);
    }

    /* plot frame */
    drawframe (gno ,layer);

    if (gtype != GRAPH_PIE) {
      if (obj_tid_is_visible_on_layer (Q_LegendBox ,-1 ,Q_Graph ,gno ,layer)) {
        /* plot legends */
        dolegend (gno);
      }
    }

    /* draw title and subtitle */
    if (layer_is_active (layer)) {
      draw_titles (gno ,layer);
    }

    /* draw regions and mark the reference points only if in interactive mode */
    if (terminal_device() == TRUE) {
        draw_regions(gno);
        draw_ref_point(gno);
    }
}

void draw_smith_chart (int gno ,int layer)
{
}

void draw_pie_chart (int gno ,int layer)
{
    int i, setno, nsets = 0;
    plotarr p;
    view v;
    world w;
    int sgn;
    VPoint vpc, vp1, vp2, vps[3], vpa;
    VVector offset;
    double r, start_angle, stop_angle;
    double e_max, norm;
    double *x, *c, *e, *pt;
    AValue avalue;
    char str[MAX_STRING_LENGTH];

    get_graph_viewport(gno, &v);
    vpc.x = (v.xv1 + v.xv2)/2;
    vpc.y = (v.yv1 + v.yv2)/2;

    get_graph_world(gno, &w);
    sgn = is_graph_xinvert(gno) ? -1:1;

    for (setno = 0; setno < number_of_sets(gno); setno++) {
        if (is_set_drawable(gno, setno)) {
	  if (layer_this_set_is_not_visible (layer ,gno ,setno)) continue;
            nsets++;
            if (nsets > 1) {
                errmsg("Only one set per pie chart can be drawn");
                return;
            }

            switch (dataset_type(gno, setno)) {
            case SET_XY:
            case SET_XYCOLOR:
            case SET_XYCOLPAT:
                get_graph_plotarr(gno, setno, &p);
                /* data */
                x = getcol(gno, setno, DATA_X);
                /* explode factor */
                e = getcol(gno, setno, DATA_Y);
                /* colors */
                c = getcol(gno, setno, DATA_Y1);
                /* patterns */
                pt = getcol(gno, setno, DATA_Y2);

                /* get max explode factor */
                e_max = 0.0;
                for (i = 0; i < p.data.en[0]; i++) {
                    e_max = MAX2(e_max, e[i]);
                }

                r = 0.8/(1.0 + e_max)*MIN2(v.xv2 - v.xv1, v.yv2 - v.yv1)/2;

                norm = 0.0;
                for (i = 0; i < p.data.en[0]; i++) {
                    if (x[i] < 0.0) {
                        errmsg("No negative values in pie charts allowed");
                        return;
                    }
                    if (e[i] < 0.0) {
                        errmsg("No negative offsets in pie charts allowed");
                        return;
                    }
                    norm += x[i];
                }

                stop_angle = w.xg1;
                for (i = 0; i < p.data.en[0]; i++) {
                    Pen pen;

                    start_angle = stop_angle;
                    stop_angle = start_angle + sgn*2*M_PI*x[i]/norm;
                    offset.x = e[i]*r*cos((start_angle + stop_angle)/2.0);
                    offset.y = e[i]*r*sin((start_angle + stop_angle)/2.0);
                    vps[0].x = vpc.x + r*cos(start_angle) + offset.x;
                    vps[0].y = vpc.y + r*sin(start_angle) + offset.y;
                    vps[1].x = vpc.x + offset.x;
                    vps[1].y = vpc.y + offset.y;
                    vps[2].x = vpc.x + r*cos(stop_angle) + offset.x;
                    vps[2].y = vpc.y + r*sin(stop_angle) + offset.y;
                    vp1.x = vpc.x - r + offset.x;
                    vp1.y = vpc.y - r + offset.y;
                    vp2.x = vpc.x + r + offset.x;
                    vp2.y = vpc.y + r + offset.y;

                    if (c != NULL) {
                        pen.color   = (int) rint(c[i]);
                    } else {
                        pen.color = p.symfillpen.color;
                    }
                    if (pt != NULL) {
                        pen.pattern   = (int) rint(pt[i]);
                    } else {
                        pen.pattern = p.symfillpen.pattern;
                    }
                    setpen(pen);
                    DrawFilledArc(vp1, vp2,
                        (int) rint(180.0/M_PI*start_angle),
                        (int) rint(180.0/M_PI*stop_angle),
                        ARCFILL_PIESLICE);

                    setpen(p.sympen);
                    setlinewidth(p.symlinew);
                    setlinestyle(p.symlines);
                    DrawPolyline(vps, 3, POLYLINE_OPEN);
                    DrawArc(vp1, vp2,
                        (int) rint(180.0/M_PI*start_angle),
                        (int) rint(180.0/M_PI*stop_angle));

                    avalue = p.avalue;

                    if (avalue.active == TRUE) {

                        vpa.x = vpc.x + ((1 + e[i])*r + avalue.offset.y)*
                            cos((start_angle + stop_angle)/2.0);
                        vpa.y = vpc.y + ((1 + e[i])*r + avalue.offset.y)*
                            sin((start_angle + stop_angle)/2.0);

                        strcpy(str, avalue.prestr);

                        switch(avalue.type) {
                        case AVALUE_TYPE_X:
                            strcat(str, create_fstring(avalue.format, avalue.prec, x[i],
                                                                 LFORMAT_TYPE_EXTENDED));
                            break;
                        case AVALUE_TYPE_STRING:
                            if (p.data.s != NULL && p.data.s[i] != NULL) {
                                strcat(str, p.data.s[i]);
                            }
                            break;
                        default:
                            continue;
                        }

                        strcat(str, avalue.appstr);

                        setcharsize(avalue.size);
                        setfont(avalue.font);
                        setcolor(avalue.color);

                        WriteString(vpa, avalue.angle, JUST_CENTER|JUST_MIDDLE, str);
                    }
                }
                break;
            default:
                errmsg("Unsupported in pie chart set type");
                break;
            }
        }
    }
}

void draw_polar_graph (int gno ,int layer)
{
    int i;
    plotarr p;

    for (i = 0; i < number_of_sets(gno); i++) {
        if (is_set_drawable(gno, i)) {
	  if (layer_this_set_is_not_visible (layer ,gno ,i)) continue;
            get_graph_plotarr(gno, i, &p);
            switch (dataset_type(gno, i)) {
            case SET_XY:
            case SET_XYSIZE:
            case SET_XYCOLOR:
            case SET_XYZ:
                drawsetline(gno, i, &p, 0, NULL, NULL, 0.0);
                drawsetsyms(gno, i, &p, 0, NULL, NULL, 0.0);
                drawsetavalues(gno, i, &p, 0, NULL, NULL, 0.0);
                break;
            default:
                errmsg("Unsupported in polar graph set type");
                break;
            }
        }
    }
}

void xyplot (int gno ,int layer)
{
    int i, j;
    plotarr p;
    int refn;
    double *refx, *refy;
    double offset, epsilon;
  QDobject *po;

    refn = 0;
    offset = 0.0;
    refx = NULL;
    refy = NULL;

    /* draw sets */
    switch (get_graph_type(gno)) {
    case GRAPH_XY:
        for (i = 0; i < number_of_sets(gno); i++) {
            if (is_set_drawable(gno, i)) {
	      if (layer_this_set_is_not_visible (layer ,gno ,i)) continue;
                get_graph_plotarr(gno, i, &p);
                switch (dataset_type(gno, i)) {
                case SET_XY:
                case SET_XYSIZE:
                case SET_XYCOLOR:
                case SET_XYZ:
                    drawsetline(gno, i, &p, 0, NULL, NULL, 0.0);
                    drawsetsyms(gno, i, &p, 0, NULL, NULL, 0.0);
                    drawsetavalues(gno, i, &p, 0, NULL, NULL, 0.0);
                    break;
                case SET_BAR:
                    drawsetline(gno, i, &p, 0, NULL, NULL, 0.0);
                    drawsetbars(gno, i, &p, 0, NULL, NULL, 0.0);
                    drawsetavalues(gno, i, &p, 0, NULL, NULL, 0.0);
                    break;
                case SET_BARDY:
                case SET_BARDYDY:
                    drawsetline(gno, i, &p, refn, refx, refy, offset);
                    drawsetbars(gno, i, &p, refn, refx, refy, offset);
                    drawseterrbars(gno, i, &p, refn, refx, refy, offset);
                    drawsetavalues(gno, i, &p, refn, refx, refy, offset);
                    break;
                case SET_XYDX:
                case SET_XYDY:
                case SET_XYDXDX:
                case SET_XYDYDY:
                case SET_XYDXDY:
                case SET_XYDXDXDYDY:
                    drawsetline(gno, i, &p, 0, NULL, NULL, 0.0);
                    drawseterrbars(gno, i, &p, 0, NULL, NULL, 0.0);
                    drawsetsyms(gno, i, &p, 0, NULL, NULL, 0.0);
                    drawsetavalues(gno, i, &p, 0, NULL, NULL, 0.0);
                    break;
                case SET_XYHILO:
                    drawsethilo(&p);
                    drawsetavalues(gno, i, &p, 0, NULL, NULL, 0.0);
                    break;
                case SET_XYCMAP:
		  if (zc_init (gno ,i ,&p)) {
		    zc_draw_set_xycmap (gno ,i ,&p ,layer);
		  }
		  break;
                case SET_XYVMAP:
                    drawsetline (gno, i, &p, 0, NULL, NULL, 0.0);
		    po = obj_tid_get_pointer (Q_Set ,i ,gno);
                    drawsetvmap (gno, &p ,po);
                    drawsetsyms (gno, i, &p, 0, NULL, NULL, 0.0);
                    drawsetavalues(gno, i, &p, 0, NULL, NULL, 0.0);
                    break;
                case SET_BOXPLOT:
                    drawsetline(gno, i, &p, 0, NULL, NULL, 0.0);
                    drawsetboxplot(&p);
                    drawsetavalues(gno, i, &p, 0, NULL, NULL, 0.0);
                    break;
                default:
                    errmsg("Unsupported in XY graph set type");
                    break;
                }
            }
        }
        break;
    case GRAPH_CHART:
        for (i = 0; i < number_of_sets(gno); i++) {
	  if (layer_this_set_is_not_visible (layer ,gno ,i)) continue;
            get_graph_plotarr(gno, i, &p);
            if (is_set_drawable(gno, i)) {
                if (p.data.en[0] > refn) {
                    refn = p.data.en[0];
                    refx = p.data.ex[0];
                }
                if (is_graph_stacked(gno) != TRUE) {
                    offset -= 0.5*0.02*p.symsize;
                }
            }
        }
        offset -= 0.5*(nactive(gno) - 1)*get_graph_bargap(gno);

        if (is_graph_stacked(gno) == TRUE) {
            refy = xcalloc(refn, SIZEOF_DOUBLE);
            if (refy == NULL) {
                return;
            }
        }

        if (refx) {
            double xmin, xmax;
            int imin, imax;
            minmax(refx, refn, &xmin, &xmax, &imin, &imax);
            epsilon = 1.0e-3*(xmax - xmin)/refn;
        } else {
            epsilon = 0.0;
        }

        for (i = 0; i < number_of_sets(gno); i++) {
            int x_ok;
            double *x;

            get_graph_plotarr(gno, i, &p);
            if (is_set_drawable(gno, i)) {
                /* check that abscissas are identical with refx */
                x = getcol(gno, i, DATA_X);
                x_ok = TRUE;
                for (j = 0; j < getsetlength(gno, i); j++) {
                    if (fabs(x[j] - refx[j]) > epsilon) {
                        x_ok = FALSE;
                        break;
                    }
                }
                if (x_ok != TRUE) {
                    char buf[128];
                    sprintf(buf, "Set G%d.S%d has different abscissas, "
                                 "skipped from the chart.", gno, i);
                    errmsg(buf);
                    continue;
                }

                if (is_graph_stacked(gno) != TRUE) {
                    offset += 0.5*0.02*p.symsize;
                }
                switch (dataset_type(gno, i)) {
                case SET_XY:
                case SET_XYSIZE:
                case SET_XYCOLOR:
                    drawsetline(gno, i, &p, refn, refx, refy, offset);
                    if (is_graph_stacked(gno) != TRUE) {
                        drawsetsyms(gno, i, &p, refn, refx, refy, offset);
                        drawsetavalues(gno, i, &p, refn, refx, refy, offset);
                    }
                    break;
                case SET_BAR:
                    drawsetline(gno, i, &p, refn, refx, refy, offset);
                    drawsetbars(gno, i, &p, refn, refx, refy, offset);
                    if (is_graph_stacked(gno) != TRUE) {
                        drawsetavalues(gno, i, &p, refn, refx, refy, offset);
                    }
                    break;
                case SET_BARDY:
                case SET_BARDYDY:
                    drawsetline(gno, i, &p, refn, refx, refy, offset);
                    drawsetbars(gno, i, &p, refn, refx, refy, offset);
                    if (is_graph_stacked(gno) != TRUE) {
                        drawseterrbars(gno, i, &p, refn, refx, refy, offset);
                        drawsetavalues(gno, i, &p, refn, refx, refy, offset);
                    }
                    break;
                case SET_XYDY:
                case SET_XYDYDY:
                    drawsetline(gno, i, &p, refn, refx, refy, offset);
                    if (is_graph_stacked(gno) != TRUE) {
                        drawseterrbars(gno, i, &p, refn, refx, refy, offset);
                        drawsetsyms(gno, i, &p, refn, refx, refy, offset);
                        drawsetavalues(gno, i, &p, refn, refx, refy, offset);
                    }
                    break;
                default:
                    errmsg("Unsupported in XY chart set type");
		    return;
                }
                if (is_graph_stacked(gno) != TRUE) {
                    offset += 0.5*0.02*p.symsize + get_graph_bargap(gno);
                } else {
                    for (j = 0; j < p.data.en[0]; j++) {
                        refy[j] += p.data.ex[1][j];
                    }
                }
            }
        }

        if (is_graph_stacked(gno) == TRUE) {
            /* Second pass for stacked charts: symbols and avalues */
            offset = 0.0;
            for (j = 0; j < refn; j++) {
                refy[j] = 0.0;
            }

            for (i = 0; i < number_of_sets(gno); i++) {
	      if (layer_this_set_is_not_visible (layer ,gno ,i)) continue;
                get_graph_plotarr(gno, i, &p);
                if (is_set_drawable(gno, i)) {
                    switch (dataset_type(gno, i)) {
                    case SET_XY:
                    case SET_XYSIZE:
                    case SET_XYCOLOR:
                        drawsetsyms(gno, i, &p, refn, refx, refy, offset);
                        drawsetavalues(gno, i, &p, refn, refx, refy, offset);
                        break;
                    case SET_BAR:
                        drawsetavalues(gno, i, &p, refn, refx, refy, offset);
                        break;
                    case SET_BARDY:
                    case SET_BARDYDY:
                        drawseterrbars(gno, i, &p, refn, refx, refy, offset);
                        drawsetavalues(gno, i, &p, refn, refx, refy, offset);
                        break;
                    case SET_XYDY:
                    case SET_XYDYDY:
                        drawseterrbars(gno, i, &p, refn, refx, refy, offset);
                        drawsetsyms(gno, i, &p, refn, refx, refy, offset);
                        drawsetavalues(gno, i, &p, refn, refx, refy, offset);
                        break;
                    }

                    for (j = 0; j < p.data.en[0]; j++) {
                        refy[j] += p.data.ex[1][j];
                    }
                }
            }
        }

        if (refy != NULL) {
            xfree(refy);
        }
        break;
    case GRAPH_FIXED:
        for (i = 0; i < number_of_sets(gno); i++) {
	  if (layer_this_set_is_not_visible (layer ,gno ,i)) continue;
            if (is_set_drawable(gno, i)) {
                get_graph_plotarr(gno, i, &p);
                switch (dataset_type(gno, i)) {
                case SET_XY:
                case SET_XYSIZE:
                case SET_XYCOLOR:
                case SET_XYZ:
                    drawsetline(gno, i, &p, 0, NULL, NULL, 0.0);
                    drawsetsyms(gno, i, &p, 0, NULL, NULL, 0.0);
                    drawsetavalues(gno, i, &p, 0, NULL, NULL, 0.0);
                    break;
                case SET_XYDX:
                case SET_XYDY:
                case SET_XYDXDX:
                case SET_XYDYDY:
                case SET_XYDXDY:
                case SET_XYDXDXDYDY:
                    drawsetline(gno, i, &p, 0, NULL, NULL, 0.0);
                    drawseterrbars(gno, i, &p, 0, NULL, NULL, 0.0);
                    drawsetsyms(gno, i, &p, 0, NULL, NULL, 0.0);
                    drawsetavalues(gno, i, &p, 0, NULL, NULL, 0.0);
                    break;
                case SET_XYR:
                    drawcirclexy(&p);
                    drawsetsyms(gno, i, &p, 0, NULL, NULL, 0.0);
                    drawsetavalues(gno, i, &p, 0, NULL, NULL, 0.0);
                    break;
                case SET_XYCMAP:
		  if (zc_init (gno ,i ,&p)) {
		    setpen  (p.linepen);
		    zc_draw_set_xycmap (gno ,i ,&p ,layer);
		  }
		  break;
                case SET_XYVMAP:
                    drawsetline(gno, i, &p, 0, NULL, NULL, 0.0);
		    po = obj_tid_get_pointer (Q_Set ,i ,gno);
                    drawsetvmap(gno, &p ,po);
                    drawsetsyms(gno, i, &p, 0, NULL, NULL, 0.0);
                    drawsetavalues(gno, i, &p, 0, NULL, NULL, 0.0);
		  break;
                default:
                    errmsg("Unsupported in XY graph fixed set type");
                    break;
                }
            }
        }
        break;
    } /* end g.type */

}

void draw_regions(int gno)
{
    int i;

    setclipping(TRUE);

    /* draw any defined regions for this graph */
    for (i = 0; i < MAXREGION; i++) {
        if (rg[i].active && rg[i].linkto == gno) {
            setcolor(rg[i].color);
            setpattern(1);
            setlinewidth(rg[i].linew);
            setlinestyle(rg[i].lines);
            draw_region(i);
        }
    }
}

void draw_ref_point(int gno)
{
    GLocator locator;
    WPoint wp;
    VPoint vp;

    if (is_refpoint_active(gno)) {
        get_graph_locator(gno, &locator);
        wp.x = locator.dsx;
        wp.y = locator.dsy;
        vp = Wpoint2Vpoint(wp);
        setcolor(1);
        setpattern(1);
        setlinewidth(1.0);
        setlinestyle(1);
        symplus(vp, 0.01);
        DrawCircle (vp, 0.01);
    }
}


/* draw title and subtitle */
void draw_titles (int gno ,int layer)
{
    view v;
    labels lab;
    VPoint vp1, vp2;
    int jus;

    get_graph_viewport(gno, &v);
    get_graph_labels(gno, &lab);

    vp1.x = (v.xv2 + v.xv1) / 2;
    vp1.y = (v.yv2 < v.yv1)? v.yv1 : v.yv2;
    vp2 = vp1;

    if (lab.title.s && lab.title.s[0] && lab.title.layer == layer) {
        setcolor(lab.title.color);
        setcharsize(lab.title.charsize);
        setfont(lab.title.font);
	vp1.x += lab.title.x1;
        vp1.y += lab.title.y1 + 0.06;
	jus = get_just (lab.title.just);
        WriteString(vp1 ,0 ,jus ,lab.title.s);
    }
    if (lab.stitle.s && lab.stitle.s[0] && lab.stitle.layer == layer) {
        setcolor(lab.stitle.color);
        setcharsize(lab.stitle.charsize);
        setfont(lab.stitle.font);
	vp2.x += lab.stitle.x1;
        vp2.y += lab.stitle.y1 + 0.02;
	jus = get_just (lab.stitle.just);
        WriteString(vp2 ,0 ,jus ,lab.stitle.s);
    }
}

/*
 * draw the graph frame
 */
void drawframe (int gno ,int layer)
{
    view v;
    framep f;
    VPoint vps[4];

    if (layer_is_active (layer) == FALSE) return;

    get_graph_framep   (gno, &f);
    get_graph_viewport (gno, &v);
    if (layer != f.layer) return;

    setpen(f.pen);
    setlinewidth(f.linew);
    setlinestyle(f.lines);

    switch (f.type) {
    case 0:
        vps[0].x = v.xv1;
        vps[0].y = v.yv1;
        vps[1].x = v.xv2;
        vps[1].y = v.yv2;
        DrawRect(vps[0], vps[1]);
        break;
    case 1:                     /* half open */
        vps[0].x = v.xv1;
        vps[0].y = v.yv2;
        vps[1].x = v.xv1;
        vps[1].y = v.yv1;
        vps[2].x = v.xv2;
        vps[2].y = v.yv1;
        DrawPolyline(vps, 3, POLYLINE_OPEN);
        break;
    case 2:                     /* break top */
        vps[0].x = v.xv1;
        vps[0].y = v.yv2;
        vps[1].x = v.xv1;
        vps[1].y = v.yv1;
        vps[2].x = v.xv2;
        vps[2].y = v.yv1;
        vps[3].x = v.xv2;
        vps[3].y = v.yv2;
        DrawPolyline(vps, 4, POLYLINE_OPEN);
        break;
    case 3:                     /* break bottom */
        vps[0].x = v.xv1;
        vps[0].y = v.yv1;
        vps[1].x = v.xv1;
        vps[1].y = v.yv2;
        vps[2].x = v.xv2;
        vps[2].y = v.yv2;
        vps[3].x = v.xv2;
        vps[3].y = v.yv1;
        DrawPolyline(vps, 4, POLYLINE_OPEN);
        break;
    case 4:                     /* break left */
        vps[0].x = v.xv1;
        vps[0].y = v.yv1;
        vps[1].x = v.xv2;
        vps[1].y = v.yv1;
        vps[2].x = v.xv2;
        vps[2].y = v.yv2;
        vps[3].x = v.xv1;
        vps[3].y = v.yv2;
        DrawPolyline(vps, 4, POLYLINE_OPEN);
        break;
    case 5:                     /* break right */
        vps[0].x = v.xv2;
        vps[0].y = v.yv1;
        vps[1].x = v.xv1;
        vps[1].y = v.yv1;
        vps[2].x = v.xv1;
        vps[2].y = v.yv2;
        vps[3].x = v.xv2;
        vps[3].y = v.yv2;
        DrawPolyline(vps, 4, POLYLINE_OPEN);
        break;
    }
}

void fillframe (int gno ,int layer)
{
    view v;
    framep f;
    VPoint vp1, vp2;

    if (layer_is_active (layer) == FALSE) return;

    get_graph_framep   (gno, &f);
    get_graph_viewport (gno, &v);
    if (layer != f.layer) return;

    /* fill coordinate frame with background color */
    if (f.fillpen.pattern != 0) {
        setpen(f.fillpen);
        vp1.x = v.xv1;
        vp1.y = v.yv1;
        vp2.x = v.xv2;
        vp2.y = v.yv2;
        FillRect(vp1, vp2);
    }
}

/*
 * draw a set filling polygon
 */
void drawsetfill(int gno, int setno, plotarr *p,
                 int refn, double *refx, double *refy, double offset)
{
  int i, len, setlen, polylen ,n_interp;
    int line_type = p->linet;
    double *x, *y ,*xint ,*yint;
    double ybase;
    world w;
    WPoint wptmp;
    VPoint *vps;
    double xmin, xmax, ymin, ymax ,start ,stop;
    int imin ,imax;
    double *mesh;
    int stacked_chart;
    int interp_setno;
    plotarr pspline;
    QDobject *po = obj_tid_get_pointer (Q_Set ,setno ,gno);

    if (p->filltype == SETFILL_NONE) {
        return;
    }

    if (get_graph_type(gno) == GRAPH_CHART) {
        x = refx;
        len = setlen = MIN2(p->data.en[0], refn);
    } else {
      x = p->data.ex[0];
      len = setlen = p->data.en[0];
    }
    y = p->data.ex[1];

    if (get_graph_type(gno) == GRAPH_CHART && is_graph_stacked(gno) == TRUE) {
        stacked_chart = TRUE;
    } else {
        stacked_chart = FALSE;
    }

    setclipping(TRUE);

    get_graph_world(gno, &w);
    vps = NULL;
    switch (line_type) {
    case LINE_TYPE_STRAIGHT:
    case LINE_TYPE_SEGMENT2:
    case LINE_TYPE_SEGMENT3:
        if (stacked_chart == TRUE && p->filltype == SETFILL_BASELINE) {
            len = 2*setlen;
        } else {
            len = setlen;
        }
        vps = (VPoint *) xmalloc((len + 2) * sizeof(VPoint));
        if (vps == NULL) {
            errmsg("Can't xmalloc in drawsetfill");
            return;
        }

        for (i = 0; i < setlen; i++) {
            wptmp.x = x[i];
            wptmp.y = y[i];
            if (stacked_chart == TRUE) {
                wptmp.y += refy[i];
            }
            vps[i] = Wpoint2Vpoint(wptmp);
    	    vps[i].x += offset;
        }
        if (stacked_chart == TRUE && p->filltype == SETFILL_BASELINE) {
            for (i = 0; i < setlen; i++) {
                wptmp.x = x[setlen - i - 1];
                wptmp.y = refy[setlen - i - 1];
                vps[setlen + i] = Wpoint2Vpoint(wptmp);
    	        vps[setlen + i].x += offset;
            }
        }
        break;
    case LINE_TYPE_LEFTSTAIR:
    case LINE_TYPE_RIGHTSTAIR:
        len = 2*setlen - 1;
        vps = (VPoint *) xmalloc((len + 2) * sizeof(VPoint));
        if (vps == NULL) {
            errmsg("Can't xmalloc in drawsetfill");
            return;
        }

        for (i = 0; i < setlen; i++) {
            wptmp.x = x[i];
            wptmp.y = y[i];
            if (stacked_chart == TRUE) {
                wptmp.y += refy[i];
            }
            vps[2*i] = Wpoint2Vpoint(wptmp);
    	    vps[2*i].x += offset;
        }
        for (i = 1; i < len; i += 2) {
            if (line_type == LINE_TYPE_LEFTSTAIR) {
                vps[i].x = vps[i - 1].x;
                vps[i].y = vps[i + 1].y;
            } else {
                vps[i].x = vps[i + 1].x;
                vps[i].y = vps[i - 1].y;
            }
        }
        break;
    case LINE_TYPE_SPLINE:
      if (p->method == INTERP_XYSPLINE) {
	/* the final number of points is computed by the interpolation routine,
	 * thus we start by the interpolation */
	xint = xcalloc (setlen ,SIZEOF_DOUBLE);
	yint = xcalloc (setlen ,SIZEOF_DOUBLE);
	if (xspline_eval_xy (&xint ,&yint ,setlen ,x ,y ,setlen ,&n_interp ,po->xspline_s ,po->xspline_p) == RETURN_FAILURE) {
	  xfree (xint);
	  xfree (yint);
	  break;
	}
	interp_setno = make_new_set (gno);  /*  make a temporary interpolating set */
	copyset (gno ,setno ,gno ,interp_setno);
	killsetdata (gno ,interp_setno);
	setcol 	    (gno ,interp_setno ,DATA_X ,xint ,n_interp);
	setcol 	    (gno ,interp_setno ,DATA_Y ,yint ,n_interp);
	p->meshlen = n_interp;
      } else {
	interp_setno = make_new_set (gno);  /*  make a temporary interpolating set */
	minmax (x ,setlen ,&start ,&stop ,&imin ,&imax);
	if (get_graph_xscale (gno) == SCALE_LOG) {
	  mesh = allocate_mesh (xy_xconv(start) ,xy_xconv(stop) ,p->meshlen);
	  for (i = 0; i < p->meshlen; i++) {
	    mesh[i] = view2world_x (mesh[i]);
	  }
	} else {
	  mesh = allocate_mesh (start ,stop ,p->meshlen);
	}
	if (do_interp (gno ,setno ,gno ,interp_setno
		       ,mesh ,p->meshlen ,p->method ,1 ,po->xspline_s)  != RETURN_SUCCESS) {
	  errmsg ("Error in drawsetfill() interpolation");
	  destroy_last_set (gno);
	  xfree (mesh);
	  return;
	}
      }
      get_graph_plotarr (gno ,interp_setno ,&pspline);
      x = pspline.data.ex[0];
      y = pspline.data.ex[1];
      if (stacked_chart == TRUE && p->filltype == SETFILL_BASELINE) {
            len = 2*p->meshlen;
        } else {
            len = p->meshlen;
        }
        vps = (VPoint *) xmalloc((len + 2) * sizeof(VPoint));
        if (vps == NULL) {
	  errmsg("Can't xmalloc vps in drawsetfill");
	  return;
        }
        for (i = 0; i <  p->meshlen; i++) {
            wptmp.x = x[i];
            wptmp.y = y[i];
            if (stacked_chart == TRUE) {
                wptmp.y += refy[i];
            }
            vps[i] = Wpoint2Vpoint (wptmp);
    	    vps[i].x += offset;
        }
        if (stacked_chart == TRUE && p->filltype == SETFILL_BASELINE) {
            for (i = 0; i < setlen; i++) {
                wptmp.x = x[setlen - i - 1];
                wptmp.y = refy[setlen - i - 1];
                vps[setlen + i] = Wpoint2Vpoint(wptmp);
    	        vps[setlen + i].x += offset;
            }
        }
	  /* and kill it */
	destroy_last_set (gno);
	break;
    default:
        return;
    }

    switch (p->filltype) {
    case SETFILL_POLYGON:
        polylen = len;
        break;
    case SETFILL_BASELINE:
        if (stacked_chart == TRUE) {
            polylen = len;
        } else {
            getsetminmax(gno, setno, &xmin, &xmax, &ymin, &ymax);
            ybase = setybase(gno, setno);
            polylen = len + 2;
            wptmp.x = MIN2(xmax, w.xg2);
            wptmp.y = ybase;
            vps[len] = Wpoint2Vpoint(wptmp);
    	    vps[len].x += offset;
            wptmp.x = MAX2(xmin, w.xg1);
            wptmp.y = ybase;
            vps[len + 1] = Wpoint2Vpoint(wptmp);
    	    vps[len + 1].x += offset;
        }
        break;
    default:
        xfree(vps);
        return;
    }

    setpen(p->setfillpen);
    setfillrule(p->fillrule);
    DrawPolygon(vps, polylen);

    xfree(vps);
}

/*
 * draw set's connecting line
 */
void drawsetline(int gno, int setno, plotarr *p,
                 int refn, double *refx, double *refy, double offset)
{
    int setlen, len ,n_interp;
    int i, ly = p->lines;
    int line_type = p->linet;
    VPoint vps[4], *vpstmp;
    WPoint wp;
    double *x, *y ,*xs ,*ys ,*xint ,*yint;
    double lw;
    double ybase;
    double xmin, xmax, ymin, ymax ,start ,stop;
    int imin ,imax;
    double *mesh;
    int stacked_chart;
    int interp_setno;
    plotarr pspline;
    QDobject *po = obj_tid_get_pointer (Q_Set ,setno ,gno);

    if (get_graph_type(gno) == GRAPH_CHART) {
        x = refx;
        setlen = MIN2(p->data.en[0], refn);
    } else {
        x = p->data.ex[0];
        setlen = p->data.en[0];
    }
    y = p->data.ex[1];

    if (get_graph_type(gno) == GRAPH_CHART && is_graph_stacked(gno) == TRUE) {
        stacked_chart = TRUE;
    } else {
        stacked_chart = FALSE;
    }

    if (stacked_chart == TRUE) {
        ybase = 0.0;
    } else {
        ybase = setybase(gno, setno);
    }

    setclipping(TRUE);

    drawsetfill(gno, setno, p, refn, refx, refy, offset);

    setpen(p->linepen);
    setlinewidth(p->linew);
    setlinestyle(ly);

    if (stacked_chart == TRUE) {
        lw = getlinewidth();
    } else {
        lw = 0.0;
    }

/* draw the line */
    if (ly != 0 && p->linepen.pattern != 0) {
        switch (line_type) {
        case LINE_TYPE_NONE:
            break;
        case LINE_TYPE_STRAIGHT:
            vpstmp = (VPoint *) xmalloc(setlen*sizeof(VPoint));
            if (vpstmp == NULL) {
                errmsg("xmalloc failed in drawsetline()");
                break;
            }
            for (i = 0; i < setlen; i++) {
                wp.x = x[i];
                wp.y = y[i];
                if (stacked_chart == TRUE) {
                    wp.y += refy[i];
                }
                vpstmp[i] = Wpoint2Vpoint(wp);
    	        vpstmp[i].x += offset;

                vpstmp[i].y -= lw/2.0;
            }
            DrawPolyline(vpstmp, setlen, POLYLINE_OPEN);
            xfree(vpstmp);
            break;
        case LINE_TYPE_SEGMENT2:
            for (i = 0; i < setlen - 1; i += 2) {
                wp.x = x[i];
                wp.y = y[i];
                if (stacked_chart == TRUE) {
                    wp.y += refy[i];
                }
                vps[0] = Wpoint2Vpoint(wp);
    	        vps[0].x += offset;
                wp.x = x[i + 1];
                wp.y = y[i + 1];
                if (stacked_chart == TRUE) {
                    wp.y += refy[i + 1];
                }
                vps[1] = Wpoint2Vpoint(wp);
    	        vps[1].x += offset;

                vps[0].y -= lw/2.0;
                vps[1].y -= lw/2.0;

                DrawLine(vps[0], vps[1]);
            }
            break;
        case LINE_TYPE_SEGMENT3:
            for (i = 0; i < setlen - 2; i += 3) {
                wp.x = x[i];
                wp.y = y[i];
                if (stacked_chart == TRUE) {
                    wp.y += refy[i];
                }
                vps[0] = Wpoint2Vpoint(wp);
    	        vps[0].x += offset;
                wp.x = x[i + 1];
                wp.y = y[i + 1];
                if (stacked_chart == TRUE) {
                    wp.y += refy[i + 1];
                }
                vps[1] = Wpoint2Vpoint(wp);
    	        vps[1].x += offset;
                wp.x = x[i + 2];
                wp.y = y[i + 2];
                if (stacked_chart == TRUE) {
                    wp.y += refy[i + 2];
                }
                vps[2] = Wpoint2Vpoint(wp);
    	        vps[2].x += offset;
                DrawPolyline(vps, 3, POLYLINE_OPEN);

                vps[0].y -= lw/2.0;
                vps[1].y -= lw/2.0;
                vps[2].y -= lw/2.0;
            }
            if (i == setlen - 2) {
                wp.x = x[i];
                wp.y = y[i];
                if (stacked_chart == TRUE) {
                    wp.y += refy[i];
                }
                vps[0] = Wpoint2Vpoint(wp);
    	        vps[0].x += offset;
                wp.x = x[i + 1];
                wp.y = y[i + 1];
                if (stacked_chart == TRUE) {
                    wp.y += refy[i + 1];
                }
                vps[1] = Wpoint2Vpoint(wp);
    	        vps[1].x += offset;

                vps[0].y -= lw/2.0;
                vps[1].y -= lw/2.0;

                DrawLine(vps[0], vps[1]);
            }
            break;
        case LINE_TYPE_LEFTSTAIR:
        case LINE_TYPE_RIGHTSTAIR:
            len = 2*setlen - 1;
            vpstmp = (VPoint *) xmalloc(len*sizeof(VPoint));
            if (vpstmp == NULL) {
                errmsg("xmalloc failed in drawsetline()");
                break;
            }
            for (i = 0; i < setlen; i++) {
                wp.x = x[i];
                wp.y = y[i];
                if (stacked_chart == TRUE) {
                    wp.y += refy[i];
                }
                vpstmp[2*i] = Wpoint2Vpoint(wp);
    	        vpstmp[2*i].x += offset;
            }
            for (i = 1; i < len; i += 2) {
                if (line_type == LINE_TYPE_LEFTSTAIR) {
                    vpstmp[i].x = vpstmp[i - 1].x;
                    vpstmp[i].y = vpstmp[i + 1].y;
                } else {
                    vpstmp[i].x = vpstmp[i + 1].x;
                    vpstmp[i].y = vpstmp[i - 1].y;
                }
            }
            DrawPolyline(vpstmp, len, POLYLINE_OPEN);
            xfree(vpstmp);
            break;
	case LINE_TYPE_SPLINE:
	  if (p->method == INTERP_XYSPLINE) {
	    /* the final number of points is computed by the interpolation routine,
	     * thus we start by the interpolation */
	    xint = xcalloc (setlen ,SIZEOF_DOUBLE);
	    yint = xcalloc (setlen ,SIZEOF_DOUBLE);
	    if (xspline_eval_xy (&xint ,&yint ,setlen ,x ,y ,setlen ,&n_interp ,po->xspline_s ,po->xspline_p) == RETURN_FAILURE) {
	      xfree (xint);
	      xfree (yint);
	      break;
	    }
	    interp_setno = make_new_set (gno);  /*  make a temporary interpolating set */
	    copyset   (gno ,setno ,gno ,interp_setno);
	    killsetdata (gno ,interp_setno);
	    setcol    (gno ,interp_setno ,DATA_X ,xint ,n_interp);
	    setcol    (gno ,interp_setno ,DATA_Y ,yint ,n_interp);
	    p->meshlen = n_interp;

	  } else {
	    interp_setno = make_new_set (gno); /*  make a temporary interpolating set */
	    minmax (x ,setlen ,&start ,&stop ,&imin ,&imax);
	    if (get_graph_xscale (gno) == SCALE_LOG) {
	      mesh = allocate_mesh (xy_xconv(start) ,xy_xconv(stop) ,p->meshlen);
	      for (i = 0; i < p->meshlen; i++) {
		mesh[i] = view2world_x (mesh[i]);
	      }
	    } else {
	      mesh = allocate_mesh (start ,stop ,p->meshlen);
	    }
	    if (do_interp (gno ,setno ,gno ,interp_setno
			   ,mesh ,p->meshlen ,p->method ,1 ,po->xspline_s)  != RETURN_SUCCESS) {
	      errmsg ("Error in drawsetline() interpolation");
	      destroy_last_set (gno);
	      xfree (mesh);
	      return;
	    }
	  }
	  /* draw it */
	  get_graph_plotarr (gno ,interp_setno ,&pspline);
	  xs = pspline.data.ex[0];
	  ys = pspline.data.ex[1];
	  vpstmp = (VPoint *) xmalloc (p->meshlen * sizeof(VPoint));
	  for (i = 0; i < p->meshlen; i++) {
	    wp.x = xs[i];
	    wp.y = ys[i];
	    if (stacked_chart == TRUE) {
	      wp.y += refy[i];
	    }
	    vpstmp[i] = Wpoint2Vpoint(wp);
	    vpstmp[i].x += offset;

	    vpstmp[i].y -= lw/2.0;
	  }
	  DrawPolyline(vpstmp ,p->meshlen ,POLYLINE_OPEN);
	  xfree(vpstmp);
	  /* and kill it */
	  destroy_last_set (gno);
	  break;
        default:
            errmsg("Invalid line type");
            break;
        }
    }

    if (p->dropline == TRUE) {
        for (i = 0; i < setlen; i ++) {
            wp.x = x[i];
            if (stacked_chart == TRUE) {
                wp.y = refy[i];
            } else {
                wp.y = ybase;
            }
            vps[0] = Wpoint2Vpoint(wp);
    	    vps[0].x += offset;
            wp.x = x[i];
            wp.y = y[i];
            if (stacked_chart == TRUE) {
                wp.y += refy[i];
            }
            vps[1] = Wpoint2Vpoint(wp);
    	    vps[1].x += offset;

            vps[1].y -= lw/2.0;

            DrawLine(vps[0], vps[1]);
        }
    }

    getsetminmax(gno, setno, &xmin, &xmax, &ymin, &ymax);

    if (p->baseline == TRUE && stacked_chart != TRUE) {
        wp.x = xmin;
        wp.y = ybase;
        vps[0] = Wpoint2Vpoint(wp);
    	vps[0].x += offset;
        wp.x = xmax;
        vps[1] = Wpoint2Vpoint(wp);
    	vps[1].x += offset;

        DrawLine(vps[0], vps[1]);
    }
}

/* draw the symbols */
void drawsetsyms(int gno, int setno, plotarr *p,
                 int refn, double *refx, double *refy, double offset)
{
    int setlen;
    int i, sy = p->sym;
    double symsize;
    VPoint vp;
    WPoint wp;
    double *x, *y, *z, *c;
    int skip = p->symskip + 1;
    int stacked_chart;
    double znorm = get_graph_znorm(gno);

    if (get_graph_type(gno) == GRAPH_CHART) {
        x = refx;
        setlen = MIN2(p->data.en[0], refn);
    } else {
        x = p->data.ex[0];
        setlen = p->data.en[0];
    }
    y = p->data.ex[1];

    if (get_graph_type(gno) == GRAPH_CHART && is_graph_stacked(gno) == TRUE) {
        stacked_chart = TRUE;
    } else {
        stacked_chart = FALSE;
    }

    if (p->type == SET_XYSIZE) {
        if (znorm == 0.0) {
            return;
        }
        z = p->data.ex[2];
    } else {
        z = NULL;
    }

    if (p->type == SET_XYCOLOR) {
      c = p->data.ex[2];
    } else {
        c = NULL;
    }

    setclipping(FALSE);

    if ((p->sympen.pattern != 0 && p->symlines != 0) ||
                        (p->symfillpen.pattern != 0)) {

        Pen fillpen;

        setlinewidth(p->symlinew);
        setlinestyle(p->symlines);
        setfont(p->charfont);
        for (i = p->symstart; i < setlen; i += skip) {
            wp.x = x[i];
            wp.y = y[i];
            if (stacked_chart == TRUE) {
                wp.y += refy[i];
            }

            if (!is_validWPoint(wp)){
                continue;
            }

            vp = Wpoint2Vpoint(wp);
    	    vp.x += offset;

            if (z) {
                symsize = z[i]/znorm;
            } else {
                symsize = p->symsize;
            }
            if (c) {
                fillpen.color = (int) rint(c[i]);
                if (get_colortype(fillpen.color) != COLOR_MAIN) {
                    fillpen.color = 1;
                }
            } else {
                fillpen.color = p->symfillpen.color;
            }
            fillpen.pattern = p->symfillpen.pattern;
            if (drawxysym(vp, symsize, sy, p->sympen, fillpen, p->symchar)
                != RETURN_SUCCESS) {
                return;
            }
        }
    }
//03    if (p->type == SET_XYCOLOR) xfree (c);
}


/* draw the annotative values */
void drawsetavalues(int gno, int setno, plotarr *p,
                 int refn, double *refx, double *refy, double offset)
{
    int i;
    int setlen;
    double *x, *y, *z;
    WPoint wp;
    VPoint vp;
    int skip = p->avalue.skip + 1;
    AValue avalue;
    char str[MAX_STRING_LENGTH];
    int stacked_chart;

    avalue = p->avalue;
    if (avalue.active != TRUE) {
        return;
    }

    if (get_graph_type(gno) == GRAPH_CHART) {
        x = refx;
        setlen = MIN2(p->data.en[0], refn);
    } else {
        x = p->data.ex[0];
        setlen = p->data.en[0];
    }
    y = p->data.ex[1];

    if (dataset_cols(gno, setno) > 2) {
        z = p->data.ex[2];
    } else {
        z = NULL;
    }

    if (get_graph_type(gno) == GRAPH_CHART && is_graph_stacked(gno) == TRUE) {
        stacked_chart = TRUE;
    } else {
        stacked_chart = FALSE;
    }

    setcharsize(avalue.size);
    setfont(avalue.font);

    for (i = avalue.start; i < setlen; i += skip) {
        wp.x = x[i];
        wp.y = y[i];
        if (stacked_chart == TRUE) {
            wp.y += refy[i];
        }

        if (!is_validWPoint(wp)){
            continue;
        }

        vp = Wpoint2Vpoint(wp);

        vp.x += avalue.offset.x;
        vp.y += avalue.offset.y;
    	vp.x += offset;

        strcpy(str, avalue.prestr);

        switch(avalue.type) {
        case AVALUE_TYPE_NONE:
            break;
        case AVALUE_TYPE_X:
            strcat(str, create_fstring(avalue.format, avalue.prec, wp.x,
                                                 LFORMAT_TYPE_EXTENDED));
            break;
        case AVALUE_TYPE_Y:
            strcat(str, create_fstring(avalue.format, avalue.prec, wp.y,
                                                 LFORMAT_TYPE_EXTENDED));
            break;
        case AVALUE_TYPE_XY:
            strcat(str, create_fstring(avalue.format, avalue.prec, wp.x,
                                                 LFORMAT_TYPE_EXTENDED));
            strcat(str, ", ");
            strcat(str, create_fstring(avalue.format, avalue.prec, wp.y,
                                                 LFORMAT_TYPE_EXTENDED));
            break;
        case AVALUE_TYPE_STRING:
            if (p->data.s != NULL && p->data.s[i] != NULL) {
                strcat(str, p->data.s[i]);
            }
            break;
        case AVALUE_TYPE_Z:
            if (z != NULL) {
                strcat(str, create_fstring(avalue.format, avalue.prec, z[i],
                                                 LFORMAT_TYPE_EXTENDED));
            }
            break;
	case AVALUE_TYPE_INDEX:   /* P. Vincent */
                strcat(str, create_fstring(avalue.format, avalue.prec, (double)(i+1),
                                                 LFORMAT_TYPE_EXTENDED));
            break;
        default:
            errmsg("Invalid type of ann. value");
            return;
        }

        strcat(str, avalue.appstr);

        setcolor(avalue.color);
        WriteString(vp, avalue.angle, JUST_CENTER|JUST_BOTTOM, str);
    }
}

void drawseterrbars(int gno, int setno, plotarr *p,
                 int refn, double *refx, double *refy, double offset)
{
    int i, n;
    double *x, *y;
    double *dx_plus, *dx_minus, *dy_plus, *dy_minus, *dtmp;
    PlacementType ptype = p->errbar.ptype;
    WPoint wp1, wp2;
    VPoint vp1, vp2;
    int stacked_chart;
    int skip = p->symskip + 1;

    if (p->errbar.active != TRUE) {
        return;
    }

    if (get_graph_type(gno) == GRAPH_CHART) {
        x = refx;
        n = MIN2(p->data.en[0], refn);
    } else {
        x = p->data.ex[0];
        n = p->data.en[0];
    }
    y = p->data.ex[1];

    if (get_graph_type(gno) == GRAPH_CHART && is_graph_stacked(gno) == TRUE) {
        stacked_chart = TRUE;
    } else {
        stacked_chart = FALSE;
    }

    dx_plus  = NULL;
    dx_minus = NULL;
    dy_plus  = NULL;
    dy_minus = NULL;
    switch (p->type) {
    case SET_XYDX:
        dx_plus = p->data.ex[2];
        break;
    case SET_XYDY:
    case SET_BARDY:
        dy_plus = p->data.ex[2];
        break;
    case SET_XYDXDX:
        dx_plus  = p->data.ex[2];
        dx_minus = p->data.ex[3];
        break;
    case SET_XYDYDY:
    case SET_BARDYDY:
        dy_plus  = p->data.ex[2];
        dy_minus = p->data.ex[3];
        break;
    case SET_XYDXDY:
        dx_plus = p->data.ex[2];
        dy_plus = p->data.ex[3];
        break;
    case SET_XYDXDXDYDY:
        dx_plus  = p->data.ex[2];
        dx_minus = p->data.ex[3];
        dy_plus  = p->data.ex[4];
        dy_minus = p->data.ex[5];
        break;
    default:
        return;
    }

    switch (ptype) {
    case PLACEMENT_OPPOSITE:
        dtmp     = dx_minus;
        dx_minus = dx_plus;
        dx_plus  = dtmp;
        dtmp     = dy_minus;
        dy_minus = dy_plus;
        dy_plus  = dtmp;
        break;
    case PLACEMENT_BOTH:
        if (dx_minus == NULL && dy_minus == NULL) {
            dx_minus = dx_plus;
            dy_minus = dy_plus;
        }
        break;
    default:
        break;
    }

    setclipping(TRUE);

    for (i = 0; i < n; i += skip) {
        wp1.x = x[i];
        wp1.y = y[i];
        if (stacked_chart == TRUE) {
            wp1.y += refy[i];
        }
        if (is_validWPoint(wp1) == FALSE) {
            continue;
        }

        vp1 = Wpoint2Vpoint(wp1);
        vp1.x += offset;

        if (dx_plus != NULL) {
            wp2 = wp1;
            wp2.x += fabs(dx_plus[i]);
            vp2 = Wpoint2Vpoint(wp2);
            vp2.x += offset;
            drawerrorbar(vp1, vp2, &p->errbar);
        }
        if (dx_minus != NULL) {
            wp2 = wp1;
            wp2.x -= fabs(dx_minus[i]);
            vp2 = Wpoint2Vpoint(wp2);
            vp2.x += offset;
            drawerrorbar(vp1, vp2, &p->errbar);
        }
        if (dy_plus != NULL) {
            wp2 = wp1;
            wp2.y += fabs(dy_plus[i]);
            vp2 = Wpoint2Vpoint(wp2);
            vp2.x += offset;
            drawerrorbar(vp1, vp2, &p->errbar);
        }
        if (dy_minus != NULL) {
            wp2 = wp1;
            wp2.y -= fabs(dy_minus[i]);
            vp2 = Wpoint2Vpoint(wp2);
            vp2.x += offset;
            drawerrorbar(vp1, vp2, &p->errbar);
        }
    }
}

/*
 * draw hi/lo-open/close
 */
void drawsethilo(plotarr *p)
{
    int i;
    double *x = p->data.ex[0], *y1 = p->data.ex[1];
    double *y2 = p->data.ex[2], *y3 = p->data.ex[3], *y4 = p->data.ex[4];
    double ilen = 0.02*p->symsize;
    int skip = p->symskip + 1;
    WPoint wp;
    VPoint vp1, vp2;

    if (p->symlines != 0) {
        setpen(p->sympen);
        setlinewidth(p->symlinew);
        setlinestyle(p->symlines);
        for (i = 0; i < p->data.en[0]; i += skip) {
            wp.x = x[i];
            wp.y = y1[i];
            vp1 = Wpoint2Vpoint(wp);
            wp.y = y2[i];
            vp2 = Wpoint2Vpoint(wp);
            DrawLine(vp1, vp2);
            wp.y = y3[i];
            vp1 = Wpoint2Vpoint(wp);
            vp2 = vp1;
            vp2.x -= ilen;
            DrawLine(vp1, vp2);
            wp.y = y4[i];
            vp1 = Wpoint2Vpoint(wp);
            vp2 = vp1;
            vp2.x += ilen;
            DrawLine(vp1, vp2);
        }
    }
}

/*
 * draw 2D bars
 */
void drawsetbars(int gno, int setno, plotarr *p,
                 int refn, double *refx, double *refy, double offset)
{
    int i, n;
    double *x, *y;
    double lw, bw = 0.01*p->symsize;
    int skip = p->symskip + 1;
    double ybase;
    WPoint wp;
    VPoint vp1, vp2;
    int stacked_chart;

    if (get_graph_type(gno) == GRAPH_CHART) {
        x = refx;
        n = MIN2(p->data.en[0], refn);
    } else {
        x = p->data.ex[0];
        n = p->data.en[0];
    }
    y = p->data.ex[1];

    if (get_graph_type(gno) == GRAPH_CHART && is_graph_stacked(gno) == TRUE) {
        stacked_chart = TRUE;
    } else {
        stacked_chart = FALSE;
    }



    if (stacked_chart == TRUE) {
        ybase = 0.0;
    } else {
        ybase = setybase(gno, setno);
    }

    setlinewidth(p->symlinew);
    setlinestyle(p->symlines);
    if (get_graph_type(gno) == GRAPH_CHART &&
        p->symlines != 0 && p->sympen.pattern != 0) {
        lw = getlinewidth();
    } else {
        lw = 0.0;
    }

    if (p->symfillpen.pattern != 0) {
	setpen(p->symfillpen);
        for (i = 0; i < n; i += skip) {
            wp.x = x[i];
            if (stacked_chart == TRUE) {
                wp.y = refy[i];
            } else {
                wp.y = ybase;
            }
    	    vp1 = Wpoint2Vpoint(wp);
            vp1.x -= bw;
    	    vp1.x += offset;
            wp.x = x[i];
            if (stacked_chart == TRUE) {
                wp.y += y[i];
            } else {
                wp.y = y[i];
            }
    	    vp2 = Wpoint2Vpoint(wp);
            vp2.x += bw;
    	    vp2.x += offset;

            vp1.x += lw/2.0;
            vp2.x -= lw/2.0;
            vp1.y += lw/2.0;

            FillRect(vp1, vp2);
        }
    }
    if (p->symlines != 0 && p->sympen.pattern != 0) {
        setpen(p->sympen);
        for (i = 0; i < n; i += skip) {
            wp.x = x[i];
            if (stacked_chart == TRUE) {
                wp.y = refy[i];
            } else {
                wp.y = ybase;
            }
    	    vp1 = Wpoint2Vpoint(wp);
            vp1.x -= bw;
    	    vp1.x += offset;
            wp.x = x[i];
            if (stacked_chart == TRUE) {
                wp.y += y[i];
            } else {
                wp.y = y[i];
            }
    	    vp2 = Wpoint2Vpoint(wp);
            vp2.x += bw;
    	    vp2.x += offset;

            vp1.x += lw/2.0;
            vp2.x -= lw/2.0;
            vp1.y += lw/2.0;

    	    DrawRect(vp1, vp2);
        }
    }
}

void drawcirclexy(plotarr *p)
{
    int i, setlen;
    double *x, *y, *r;
    int skip = p->symskip + 1;
    WPoint wp;
    VPoint vp1, vp2;

    setclipping(TRUE);

    setlen = p->data.en[0];
    x = p->data.ex[0];
    y = p->data.ex[1];
    r = p->data.ex[2];

    setfillrule(p->fillrule);
    setlinewidth(p->linew);
    setlinestyle(p->lines);

    for (i = 0; i < setlen; i += skip) {
        wp.x = x[i];
        wp.y = y[i];
        /* TODO: remove once ellipse clipping works */
        if (!is_validWPoint(wp)){
            continue;
        }
        wp.x = x[i] - r[i];
        wp.y = y[i] - r[i];
        vp1 = Wpoint2Vpoint(wp);
        wp.x = x[i] + r[i];
        wp.y = y[i] + r[i];
        vp2 = Wpoint2Vpoint(wp);
        if (p->filltype != SETFILL_NONE) {
            setpen(p->setfillpen);
            DrawFilledEllipse(vp1, vp2);
        }
        setpen(p->linepen);
        DrawEllipse(vp1, vp2);
    }
}

/* Arrows for vector map plots */
void drawsetvmap(int gno, plotarr *p ,QDobject *po)
{
    int i, setlen;
    double znorm = get_graph_znorm(gno);
    int skip = p->symskip + 1;
    double *x, *y, *vx, *vy;
    WPoint wp;
    VPoint vp1, vp2;
    Arrow arrow = {0, 1.0, 1.0, 0.0};

    Errbar eb = p->errbar;
    int project_version = get_project_version ();

    setclipping(TRUE);

    if (znorm == 0.0) {
        return;
    }

    setlen = p->data.en[0];
    x = p->data.ex[DATA_X];
    y = p->data.ex[DATA_Y];
    vx = p->data.ex[DATA_Y1];
    vy = p->data.ex[DATA_Y2];

    arrow.length = 2*eb.barsize;

    setpen(p->errbar.pen);

    for (i = 0; i < setlen; i += skip) {
        wp.x = x[i];
        wp.y = y[i];
        if (!is_validWPoint(wp)){
            continue;
        }
        vp1 = Wpoint2Vpoint(wp);
        vp2.x = vp1.x + vx[i]/znorm;
        vp2.y = vp1.y + vy[i]/znorm;

	if (project_version < 100000) {
	  setlinewidth	 (eb.riser_linew);
	  setlinestyle	 (eb.riser_lines);
	  DrawLine       (vp1, vp2);
	  setlinewidth(eb.linew);
	  setlinestyle(eb.lines);
	  draw_arrowhead(vp1, vp2, &arrow);
	} else {
	  setlinewidth   (po->linew);
	  setlinestyle   (po->lines);
	  setcolor       (po->color);
	  DrawLine       (vp1, vp2);
	  draw_arrowhead (vp1, vp2, &(po->arrow));
	}
    }
}

void drawsetboxplot(plotarr *p)
{
    int i;
    double *x, *md, *lb, *ub, *lw, *uw;
    double size = 0.01*p->symsize;
    int skip = p->symskip + 1;
    WPoint wp;
    VPoint vp1, vp2;

    x  = p->data.ex[0];
    md = p->data.ex[1];
    lb = p->data.ex[2];
    ub = p->data.ex[3];
    lw = p->data.ex[4];
    uw = p->data.ex[5];

    setclipping(TRUE);

    for (i = 0; i < p->data.en[0]; i += skip) {
        wp.x =  x[i];

        wp.y = lb[i];
        vp1 = Wpoint2Vpoint(wp);
        wp.y = ub[i];
        vp2 = Wpoint2Vpoint(wp);

        /* whiskers */
        if (p->errbar.active == TRUE) {
            VPoint vp3;
            wp.y = lw[i];
            vp3 = Wpoint2Vpoint(wp);
            drawerrorbar(vp1, vp3, &p->errbar);
            wp.y = uw[i];
            vp3 = Wpoint2Vpoint(wp);
            drawerrorbar(vp2, vp3, &p->errbar);
        }

        /* box */
        vp1.x -= size;
        vp2.x += size;
        setpen(p->symfillpen);
        FillRect(vp1, vp2);

        setpen(p->sympen);
        setlinewidth(p->symlinew);
        setlinestyle(p->symlines);
        DrawRect(vp1, vp2);

        /* median line */
        wp.y = md[i];
        vp2 = vp1 = Wpoint2Vpoint(wp);
        vp1.x -= size;
        vp2.x += size;
        DrawLine(vp1, vp2);
    }
}

int drawxysym(VPoint vp, double size, int symtype,
    Pen sympen, Pen symfillpen, char s)
{
    double symsize;
    VPoint vps[4];
    char buf[2];

    symsize = size*0.01;

    switch (symtype) {
    case SYM_NONE:
        break;
    case SYM_CIRCLE:
        setpen(symfillpen);
        DrawFilledCircle (vp, symsize);
        setpen(sympen);
        DrawCircle (vp, symsize);
        break;
    case SYM_SQUARE:
        symsize *= 0.85;
        vps[0].x = vp.x - symsize;
        vps[0].y = vp.y - symsize;
        vps[1].x = vps[0].x;
        vps[1].y = vp.y + symsize;
        vps[2].x = vp.x + symsize;
        vps[2].y = vps[1].y;
        vps[3].x = vps[2].x;
        vps[3].y = vps[0].y;

        setpen(symfillpen);
        DrawPolygon (vps, 4);
        setpen(sympen);
        DrawPolyline (vps, 4, POLYLINE_CLOSED);
        break;
    case SYM_DIAMOND:
        vps[0].x = vp.x;
        vps[0].y = vp.y + symsize;
        vps[1].x = vp.x - symsize;
        vps[1].y = vp.y;
        vps[2].x = vps[0].x;
        vps[2].y = vp.y - symsize;
        vps[3].x = vp.x + symsize;
        vps[3].y = vps[1].y;

        setpen(symfillpen);
        DrawPolygon (vps, 4);
        setpen(sympen);
        DrawPolyline (vps, 4, POLYLINE_CLOSED);
        break;
    case SYM_TRIANG1:
        vps[0].x = vp.x;
        vps[0].y = vp.y + 2*M_SQRT1_3*symsize;
        vps[1].x = vp.x - symsize;
        vps[1].y = vp.y - M_SQRT1_3*symsize;
        vps[2].x = vp.x + symsize;
        vps[2].y = vps[1].y;

        setpen(symfillpen);
        DrawPolygon (vps, 3);
        setpen(sympen);
        DrawPolyline (vps, 3, POLYLINE_CLOSED);
        break;
    case SYM_TRIANG2:
        vps[0].x = vp.x - 2*M_SQRT1_3*symsize;
        vps[0].y = vp.y;
        vps[1].x = vp.x + M_SQRT1_3*symsize;
        vps[1].y = vp.y - symsize;
        vps[2].x = vps[1].x;
        vps[2].y = vp.y + symsize;

        setpen(symfillpen);
        DrawPolygon (vps, 3);
        setpen(sympen);
        DrawPolyline (vps, 3, POLYLINE_CLOSED);
        break;
    case SYM_TRIANG3:
        vps[0].x = vp.x - symsize;
        vps[0].y = vp.y + M_SQRT1_3*symsize;
        vps[1].x = vp.x;
        vps[1].y = vp.y - 2*M_SQRT1_3*symsize;
        vps[2].x = vp.x + symsize;
        vps[2].y = vps[0].y;

        setpen(symfillpen);
        DrawPolygon (vps, 3);
        setpen(sympen);
        DrawPolyline (vps, 3, POLYLINE_CLOSED);
        break;
    case SYM_TRIANG4:
        vps[0].x = vp.x - M_SQRT1_3*symsize;
        vps[0].y = vp.y + symsize;
        vps[1].x = vps[0].x;
        vps[1].y = vp.y - symsize;
        vps[2].x = vp.x + 2*M_SQRT1_3*symsize;
        vps[2].y = vp.y;

        setpen(symfillpen);
        DrawPolygon (vps, 3);
        setpen(sympen);
        DrawPolyline (vps, 3, POLYLINE_CLOSED);
        break;
    case SYM_PLUS:
        setpen(sympen);
        symplus(vp, symsize);
        break;
    case SYM_X:
        setpen(sympen);
        symx(vp, symsize);
        break;
    case SYM_SPLAT:
        setpen(sympen);
        symsplat(vp, symsize);
        break;
    case SYM_CHAR:
        setcolor(sympen.color);
        buf[0] = s;
        buf[1] = '\0';
        setcharsize(size);
        WriteString(vp, 0, JUST_CENTER|JUST_MIDDLE, buf);
        break;
    default:
        errmsg("Invalid symbol type");
        return RETURN_FAILURE;
    }
    return RETURN_SUCCESS;
}

static void drawlegbarsym(VPoint vp, double size, Pen sympen, Pen symfillpen)
{
    double width, height;
    VPoint vps[4];

    width  = 0.02*size;
    height = 0.02*getcharsize();

    vps[0].x = vps[1].x = vp.x - width/2;
    vps[2].x = vps[3].x = vp.x + width/2;
    vps[0].y = vps[3].y = vp.y - height/2;
    vps[1].y = vps[2].y = vp.y + height/2;

    setpen(symfillpen);
    DrawPolygon (vps, 4);
    setpen(sympen);
    DrawPolyline (vps, 4, POLYLINE_CLOSED);
}

void drawerrorbar(VPoint vp1, VPoint vp2, Errbar *eb)
{
    double ilen;
    VPoint vp_plus, vp_minus;
    VVector lvv;
    double vlength;
    static Arrow arrow = {0, 1.0, 1.0, 0.0};

    lvv.x = vp2.x - vp1.x;
    lvv.y = vp2.y - vp1.y;

    vlength = hypot(lvv.x, lvv.y);
    if (vlength == 0.0) {
        return;
    }

    lvv.x /= vlength;
    lvv.y /= vlength;

    setpen(eb->pen);

    if (eb->arrow_clip && is_validVPoint(vp2) == FALSE) {
        vp2.x = vp1.x + eb->cliplen*lvv.x;
        vp2.y = vp1.y + eb->cliplen*lvv.y;
        setlinewidth(eb->riser_linew);
        setlinestyle(eb->riser_lines);
        DrawLine(vp1, vp2);
        arrow.length = 2*eb->barsize;
        setlinewidth(eb->linew);
        setlinestyle(eb->lines);
        draw_arrowhead(vp1, vp2, &arrow);
    } else {
        setlinewidth(eb->riser_linew);
        setlinestyle(eb->riser_lines);
        DrawLine(vp1, vp2);
        setlinewidth(eb->linew);
        setlinestyle(eb->lines);
        ilen = 0.01*eb->barsize;
        vp_minus.x = vp2.x - ilen*lvv.y;
        vp_minus.y = vp2.y + ilen*lvv.x;
        vp_plus.x  = vp2.x + ilen*lvv.y;
        vp_plus.y  = vp2.y - ilen*lvv.x;
        DrawLine(vp_minus, vp_plus);
    }
}

/* --------------------------------------------------------------- */

/*
 * draw arrow head
 */
void draw_arrowhead(VPoint vp1, VPoint vp2, const Arrow *arrowp)
{
    double L, l, d, vlength;
    VVector vnorm;
    VPoint vpc, vpl, vpr, vps[4];
    int lines;
    int fg;

    vlength = hypot((vp2.x - vp1.x), (vp2.y - vp1.y));
    if (vlength == 0.0) {
        return;
    }

    vnorm.x = (vp2.x - vp1.x)/vlength;
    vnorm.y = (vp2.y - vp1.y)/vlength;

    L = 0.01*arrowp->length;
    d = L*arrowp->dL_ff;
    l = L*arrowp->lL_ff;

    vpc.x = vp2.x - L*vnorm.x;
    vpc.y = vp2.y - L*vnorm.y;
    vpl.x = vpc.x + 0.5*d*vnorm.y;
    vpl.y = vpc.y - 0.5*d*vnorm.x;
    vpr.x = vpc.x - 0.5*d*vnorm.y;
    vpr.y = vpc.y + 0.5*d*vnorm.x;
    vpc.x += l*vnorm.x;
    vpc.y += l*vnorm.y;

    vps[0] = vpl;
    vps[1] = vp2;
    vps[2] = vpr;
    vps[3] = vpc;

    lines = getlinestyle();
    setlinestyle(1);

    switch (arrowp->type) {
    case 0:
        setpattern(1);
        DrawPolyline(vps, 3, POLYLINE_OPEN);
        break;
    case 1:
        setpattern(1);
        DrawPolygon(vps, 4);
        DrawPolyline(vps, 4, POLYLINE_CLOSED);
        break;
    case 2:
        fg = getcolor();
        setcolor(getbgcolor());
        setpattern(1);
        DrawPolygon(vps, 4);
        setcolor(fg);
        DrawPolyline(vps, 4, POLYLINE_CLOSED);
        break;
    default:
        errmsg("Internal error in draw_arrowhead()");
        break;
    }

    setlinestyle(lines);

    return;
}

void draw_region(int r)
{
    int i;
    double vshift = 0.05;
    double xshift = 0.0, yshift = 0.0;

    region *this;

    int rgndouble=0;
    Arrow arrow;

    WPoint wptmp, wp1, wp2, wp3, wp4;
    VPoint vps[4], *vpstmp;
    /* set same arrows as Q_Line */
    set_default_arrow (Q_Line ,&arrow);

    this=&rg[r];

    switch (this->type) {
    case REGION_POLYI:
    case REGION_POLYO:
        if (this->x != NULL && this->y != NULL && this->n > 2) {
            vpstmp = xmalloc (this->n*sizeof(VPoint));
            if (vpstmp == NULL) {
                errmsg("xmalloc error in draw_region()");
                return;
            } else {
                for (i = 0; i < this->n; i++) {
                    wptmp.x = this->x[i];
                    wptmp.y = this->y[i];
                    vpstmp[i] = Wpoint2Vpoint(wptmp);
                }
                DrawPolyline(vpstmp, this->n, POLYLINE_CLOSED);
		xfree(vpstmp);
            }
        }
        return;
    case REGION_ABOVE:
        xshift = 0.0;
        yshift = vshift;
        break;
    case REGION_BELOW:
        xshift = 0.0;
        yshift = -vshift;
        break;
    case REGION_TOLEFT:
        xshift = -vshift;
        yshift = 0.0;
        break;
    case REGION_TORIGHT:
        xshift = vshift;
        yshift = 0.0;
        break;
    case REGION_HORIZI:
    case REGION_HORIZO:
        wp1.x=this->x1;
	wp1.y=this->y1;
	wp2.x=this->x1;
	wp2.y=this->y2;
        wp3.x=this->x2;
	wp3.y=this->y1;
	wp4.x=this->x2;
	wp4.y=this->y2;
	rgndouble=1;
	break;
    case REGION_VERTI:
    case REGION_VERTO:
        wp1.x=this->x1;
	wp1.y=this->y1;
	wp2.x=this->x2;
	wp2.y=this->y1;
        wp3.x=this->x1;
	wp3.y=this->y2;
	wp4.x=this->x2;
	wp4.y=this->y2;
	rgndouble=1;
	break;
    default:
        errmsg("Internal error in draw_region");
        return;
    }

    if(!rgndouble) {
        wptmp.x = this->x1;
        wptmp.y = this->y1;
        vps[1] = Wpoint2Vpoint(wptmp);
        wptmp.x = this->x2;
        wptmp.y = this->y2;
        vps[2] = Wpoint2Vpoint(wptmp);
        vps[0].x = vps[1].x + xshift;
        vps[0].y = vps[1].y + yshift;
        vps[3].x = vps[2].x + xshift;
        vps[3].y = vps[2].y + yshift;
        DrawPolyline(vps, 4, POLYLINE_OPEN);
        draw_arrowhead(vps[1], vps[0], &arrow);
        draw_arrowhead(vps[2], vps[3], &arrow);
    } else {
        vps[0] = Wpoint2Vpoint(wp1);
        vps[1] = Wpoint2Vpoint(wp2);
        DrawLine(vps[0], vps[1]);
        vps[0] = Wpoint2Vpoint(wp3);
        vps[1] = Wpoint2Vpoint(wp4);
        DrawLine(vps[0], vps[1]);
        wp1.x=(wp1.x+wp2.x)/2;
        wp1.y=(wp1.y+wp2.y)/2;
        wp3.x=(wp3.x+wp4.x)/2;
        wp3.y=(wp3.y+wp4.y)/2;
        vps[0] = Wpoint2Vpoint(wp1);
        vps[1] = Wpoint2Vpoint(wp3);
        DrawLine(vps[0], vps[1]);
        draw_arrowhead(vps[0], vps[1], &arrow);
    }
}

/* ---------------------- legends ---------------------- */


/*
 * draw the legend
 */
void dolegend (int gno)
{
  int i;
    int draw_flag;
    double maxsymsize;
    double ldist, sdist, yskip;

    WPoint wptmp;
    VPoint vp, vp2;

    view v;
    legend l;
    QDobject lobj;
    plotarr p;

    get_graph_legend (gno ,&l ,&lobj);
    if (l.active == FALSE) {
        return;
    }


    maxsymsize = 0.0;
    draw_flag = FALSE;
    for (i = 0; i < number_of_sets(gno); i++) {
        if (is_set_drawable(gno, i)) {
            get_graph_plotarr(gno, i, &p);
            if (p.lstr[0] != '\0') {
                draw_flag = TRUE;
            }
            if (p.symsize > maxsymsize) {
                maxsymsize = p.symsize;
            }
        }
    }

    if (draw_flag == FALSE) {
        l.bb.xv1 = l.bb.xv2 = l.bb.yv1 = l.bb.yv2 = 0.0;
        /* The bb update shouldn't change the dirtystate flag */
        lock_dirtystate(TRUE);
        set_graph_legend (gno ,&l ,&lobj);
        lock_dirtystate(FALSE);
        return;
    }

    setclipping(FALSE);

    if (l.loctype == COORD_WORLD) {
        wptmp.x = l.legx;
        wptmp.y = l.legy;
        vp = Wpoint2Vpoint(wptmp);
    } else {
        vp.x = l.legx;
        vp.y = l.legy;
    }

    ldist = 0.01*l.len;
    sdist = 0.01*(l.hgap + maxsymsize);

    yskip = 0.01*l.vgap;

    activate_bbox(BBOX_TYPE_TEMP, TRUE);
    reset_bbox(BBOX_TYPE_TEMP);
    update_bbox(BBOX_TYPE_TEMP, vp);

    set_draw_mode(FALSE);
    putlegends(gno, vp, ldist, sdist, yskip);
    v = get_bbox(BBOX_TYPE_TEMP);

    vp2.x = vp.x + (v.xv2 - v.xv1) + 2*0.01*l.hgap;
    vp2.y = vp.y - (v.yv2 - v.yv1) - 2*0.01*l.vgap;

    l.bb.xv1 = vp.x;
    l.bb.yv1 = vp2.y;
    l.bb.xv2 = vp2.x;
    l.bb.yv2 = vp.y;
    /* The bb update shouldn't change the dirtystate flag */
    lock_dirtystate(TRUE);
    set_graph_legend (gno ,&l ,&lobj);
    lock_dirtystate(FALSE);

    set_draw_mode(TRUE);

    setpen(l.boxfillpen);
    FillRect(vp, vp2);

    if (l.boxlines != 0 && l.boxpen.pattern != 0) {
        setpen(l.boxpen);
        setlinewidth(l.boxlinew);
        setlinestyle(l.boxlines);
        DrawRect(vp, vp2);
    }

    /* correction */
    vp.x += (vp.x - v.xv1) + 0.01*l.hgap;
    vp.y += (vp.y - v.yv2) - 0.01*l.vgap;

    reset_bbox(BBOX_TYPE_TEMP);
    update_bbox(BBOX_TYPE_TEMP, vp);

    putlegends(gno, vp, ldist, sdist, yskip);
}

void putlegends(int gno, VPoint vp, double ldist, double sdist, double yskip)
{
    int i, setno;
    VPoint vp2, vpstr;
    plotarr p;
    legend l;
    QDobject lobj;

    vp2.y = vp.y;
    vp2.x = vp.x + ldist;
    vpstr.y = vp.y;
    vpstr.x = vp2.x + sdist;

    get_graph_legend (gno ,&l ,&lobj);

    for (i = 0; i < number_of_sets(gno); i++) {
        if (l.invert == FALSE) {
            setno = i;
        } else {
            setno = number_of_sets(gno) - i - 1;
        }
        if (is_set_drawable(gno, setno)) {
            get_graph_plotarr(gno, setno, &p);
	    if (p.legend_on) {

	      if (p.lstr == NULL || p.lstr[0] == '\0') {
                continue;
	      }

	      setcharsize(l.charsize);
	      setfont(l.font);
	      setcolor(l.color);
	      WriteString(vpstr, 0, JUST_LEFT|JUST_TOP, p.lstr);
	      vp.y = (vpstr.y + get_bbox(BBOX_TYPE_TEMP).yv1)/2;
	      vp2.y = vp.y;
	      vpstr.y = get_bbox(BBOX_TYPE_TEMP).yv1 - yskip;

	      setfont(p.charfont);

	      if (l.len != 0 && p.lines != 0 && p.linet != 0) {
                setpen(p.linepen);
                setlinewidth(p.linew);
                setlinestyle(p.lines);
                DrawLine(vp, vp2);

                setlinewidth(p.symlinew);
                setlinestyle(p.symlines);
                if (p.type == SET_BAR   || p.type == SET_BOXPLOT ||
                    p.type == SET_BARDY || p.type == SET_BARDYDY) {
		  drawlegbarsym(vp, p.symsize, p.sympen, p.symfillpen);
		  drawlegbarsym(vp2, p.symsize, p.sympen, p.symfillpen);
                } else {
		  drawxysym(vp, p.symsize, p.sym, p.sympen, p.symfillpen, p.symchar);
		  drawxysym(vp2, p.symsize, p.sym, p.sympen, p.symfillpen, p.symchar);
                }
	      } else {
                VPoint vptmp;
                vptmp.x = (vp.x + vp2.x)/2;
                vptmp.y = vp.y;

                setlinewidth(p.symlinew);
                setlinestyle(p.symlines);
                if (p.type == SET_BAR   || p.type == SET_BOXPLOT ||
                    p.type == SET_BARDY || p.type == SET_BARDYDY) {
		  drawlegbarsym(vptmp, p.symsize, p.sympen, p.symfillpen);
                } else {
		  drawxysym(vptmp, p.symsize, p.sym, p.sympen, p.symfillpen, p.symchar);
                }
	      }
	    }
	 }
    }
}
