Added "step=1800" and such to "DEF"
[rrdtool.git] / src / rrd_graph.c
index c58326f..f0ec67e 100644 (file)
@@ -4,18 +4,20 @@
  * rrd__graph.c  make creates ne rrds
  ****************************************************************************/
 
-#if 0
-#include "rrd_tool.h"
-#endif
 
 #include <sys/stat.h>
+
+#include "rrd_tool.h"
+
 #ifdef WIN32
 #include <io.h>
 #include <fcntl.h>
 #endif
+
 #ifdef HAVE_TIME_H
 #include <time.h>
 #endif
+
 #ifdef HAVE_LOCALE_H
 #include <locale.h>
 #endif
@@ -129,7 +131,7 @@ xtr(image_desc_t *im,time_t mytime){
 }
 
 /* translate data values into y coordinates */
-int
+double
 ytr(image_desc_t *im, double value){
     static double pixie;
     double yval;
@@ -140,26 +142,25 @@ ytr(image_desc_t *im, double value){
        pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
       yval = im->yorigin;
     } else if(!im->logarithmic) {
-      yval = im->yorigin - pixie * (value - im->minval) + 0.5;
+      yval = im->yorigin - pixie * (value - im->minval);
     } else {
       if (value < im->minval) {
        yval = im->yorigin;
       } else {
-       yval = im->yorigin - pixie * (log10(value) - log10(im->minval)) + 0.5;
+       yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
       }
     }
     /* make sure we don't return anything too unreasonable. GD lib can
        get terribly slow when drawing lines outside its scope. This is 
        especially problematic in connection with the rigid option */
     if (! im->rigid) {
-      return (int)yval;
-    } else if ((int)yval > im->yorigin) {
-      return im->yorigin+2;
-    } else if ((int) yval < im->yorigin - im->ysize){
-      return im->yorigin - im->ysize - 2;
-    } else {
-      return (int)yval;
+      /* keep yval as-is */
+    } else if (yval > im->yorigin) {
+      yval = im->yorigin+2;
+    } else if (yval < im->yorigin - im->ysize){
+      yval = im->yorigin - im->ysize - 2;
     } 
+    return yval;
 }
 
 
@@ -185,6 +186,7 @@ enum gf_en gf_conv(char *string){
     conv_if(CDEF,GF_CDEF)
     conv_if(VDEF,GF_VDEF)
     conv_if(PART,GF_PART)
+    conv_if(XPORT,GF_XPORT)
     
     return (-1);
 }
@@ -194,6 +196,7 @@ enum gfx_if_en if_conv(char *string){
     conv_if(PNG,IF_PNG)
     conv_if(SVG,IF_SVG)
     conv_if(EPS,IF_EPS)
+    conv_if(PDF,IF_PDF)
 
     return (-1);
 }
@@ -438,7 +441,73 @@ expand_range(image_desc_t *im)
 #endif
 }
 
-    
+void
+apply_gridfit(image_desc_t *im)
+{
+  if (isnan(im->minval) || isnan(im->maxval))
+    return;
+  ytr(im,DNAN);
+  if (im->logarithmic) {
+    double ya, yb, ypix, ypixfrac;
+    double log10_range = log10(im->maxval) - log10(im->minval);
+    ya = pow((double)10, floor(log10(im->minval)));
+    while (ya < im->minval)
+      ya *= 10;
+    if (ya > im->maxval)
+      return; /* don't have y=10^x gridline */
+    yb = ya * 10;
+    if (yb <= im->maxval) {
+      /* we have at least 2 y=10^x gridlines.
+        Make sure distance between them in pixels
+        are an integer by expanding im->maxval */
+      double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
+      double factor = y_pixel_delta / floor(y_pixel_delta);
+      double new_log10_range = factor * log10_range;
+      double new_ymax_log10 = log10(im->minval) + new_log10_range;
+      im->maxval = pow(10, new_ymax_log10);
+      ytr(im, DNAN); /* reset precalc */
+      log10_range = log10(im->maxval) - log10(im->minval);
+    }
+    /* make sure first y=10^x gridline is located on 
+       integer pixel position by moving scale slightly 
+       downwards (sub-pixel movement) */
+    ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
+    ypixfrac = ypix - floor(ypix);
+    if (ypixfrac > 0 && ypixfrac < 1) {
+      double yfrac = ypixfrac / im->ysize;
+      im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
+      im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
+      ytr(im, DNAN); /* reset precalc */
+    }
+  } else {
+    /* Make sure we have an integer pixel distance between
+       each minor gridline */
+    double ypos1 = ytr(im, im->minval);
+    double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
+    double y_pixel_delta = ypos1 - ypos2;
+    double factor = y_pixel_delta / floor(y_pixel_delta);
+    double new_range = factor * (im->maxval - im->minval);
+    double gridstep = im->ygrid_scale.gridstep;
+    double minor_y, minor_y_px, minor_y_px_frac;
+    im->maxval = im->minval + new_range;
+    ytr(im, DNAN); /* reset precalc */
+    /* make sure first minor gridline is on integer pixel y coord */
+    minor_y = gridstep * floor(im->minval / gridstep);
+    while (minor_y < im->minval)
+      minor_y += gridstep;
+    minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
+    minor_y_px_frac = minor_y_px - floor(minor_y_px);
+    if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
+      double yfrac = minor_y_px_frac / im->ysize;
+      double range = im->maxval - im->minval;
+      im->minval = im->minval - yfrac * range;
+      im->maxval = im->maxval - yfrac * range;
+      ytr(im, DNAN); /* reset precalc */
+    }
+    calc_horizontal_grid(im); /* recalc with changed im->maxval */
+  }
+}
+
 /* reduce data reimplementation by Alex */
 
 void
@@ -612,8 +681,9 @@ for (col=0;col<row_cnt;col++) {
 int
 data_fetch( image_desc_t *im )
 {
-    int       i,ii;
-    int skip;
+    int                i,ii;
+    int                skip;
+
     /* pull the data from the log files ... */
     for (i=0;i<im->gdes_c;i++){
        /* only GF_DEF elements fetch data */
@@ -622,13 +692,17 @@ data_fetch( image_desc_t *im )
 
        skip=0;
        /* do we have it already ?*/
-       for (ii=0;ii<i;ii++){
+       for (ii=0;ii<i;ii++) {
            if (im->gdes[ii].gf != GF_DEF) 
                continue;
-           if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
-               && (im->gdes[i].cf == im->gdes[ii].cf)){
-               /* OK the data it is here already ... 
-                * we just copy the header portion */
+           if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
+                       && (im->gdes[i].cf    == im->gdes[ii].cf)
+                       && (im->gdes[i].start == im->gdes[ii].start)
+                       && (im->gdes[i].end   == im->gdes[ii].end)
+                       && (im->gdes[i].step  == im->gdes[ii].step)) {
+               /* OK, the data is already there.
+               ** Just copy the header portion
+               */
                im->gdes[i].start = im->gdes[ii].start;
                im->gdes[i].end = im->gdes[ii].end;
                im->gdes[i].step = im->gdes[ii].step;
@@ -746,6 +820,8 @@ data_calc( image_desc_t *im){
         * so CDEFs can use VDEFs and vice versa
         */
        switch (im->gdes[gdi].gf) {
+           case GF_XPORT:
+             break;
            case GF_VDEF:
                /* A VDEF has no DS.  This also signals other parts
                 * of rrdtool that this is a VDEF value, not a CDEF.
@@ -962,6 +1038,7 @@ data_proc( image_desc_t *im ){
            case GF_CDEF:
            case GF_VDEF:
            case GF_PART:
+           case GF_XPORT:
                break;
            }
        }
@@ -1243,6 +1320,7 @@ print_calc(image_desc_t *im, char ***prdata)
        case GF_CDEF:       
        case GF_VDEF:       
        case GF_PART:
+       case GF_XPORT:
            break;
        }
     }
@@ -1385,21 +1463,15 @@ leg_place(image_desc_t *im)
 
 
 int
-horizontal_grid(image_desc_t   *im)
+calc_horizontal_grid(image_desc_t   *im)
 {
     double   range;
     double   scaledrange;
     int      pixel,i;
-    int      sgrid,egrid;
-    double   gridstep;
-    double   scaledstep;
-    char     graph_label[100];
-    double   X0,X1,Y0;
-    int      labfact,gridind;
+    int      gridind;
     int      decimals, fractionals;
-    char     labfmt[64];
 
-    labfact=2;
+    im->ygrid_scale.labfact=2;
     gridind=-1;
     range =  im->maxval - im->minval;
     scaledrange = range / im->magfact;
@@ -1421,25 +1493,25 @@ horizontal_grid(image_desc_t   *im)
            
            fractionals = floor(log10(range));
            if(fractionals < 0) /* small amplitude. */
-               sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
+               sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
            else
-               sprintf(labfmt, "%%%d.1f", decimals + 1);
-           gridstep = pow((double)10, (double)fractionals);
-           if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
-               gridstep = 0.1;
+               sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
+           im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
+           if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
+               im->ygrid_scale.gridstep = 0.1;
            /* should have at least 5 lines but no more then 15 */
-           if(range/gridstep < 5)
-                gridstep /= 10;
-           if(range/gridstep > 15)
-                gridstep *= 10;
-           if(range/gridstep > 5) {
-               labfact = 1;
-               if(range/gridstep > 8)
-                   labfact = 2;
+           if(range/im->ygrid_scale.gridstep < 5)
+                im->ygrid_scale.gridstep /= 10;
+           if(range/im->ygrid_scale.gridstep > 15)
+                im->ygrid_scale.gridstep *= 10;
+           if(range/im->ygrid_scale.gridstep > 5) {
+               im->ygrid_scale.labfact = 1;
+               if(range/im->ygrid_scale.gridstep > 8)
+                   im->ygrid_scale.labfact = 2;
            }
            else {
-               gridstep /= 5;
-               labfact = 5;
+               im->ygrid_scale.gridstep /= 5;
+               im->ygrid_scale.labfact = 5;
            }
        }
        else {
@@ -1453,33 +1525,40 @@ horizontal_grid(image_desc_t   *im)
            
            for(i=0; i<4;i++) {
               if (pixel * ylab[gridind].lfac[i] >=  2 * im->text_prop[TEXT_PROP_AXIS].size) {
-                 labfact =  ylab[gridind].lfac[i];
+                 im->ygrid_scale.labfact =  ylab[gridind].lfac[i];
                  break;
               }                          
            } 
            
-           gridstep = ylab[gridind].grid * im->magfact;
+           im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
        }
     } else {
-       gridstep = im->ygridstep;
-       labfact = im->ylabfact;
+       im->ygrid_scale.gridstep = im->ygridstep;
+       im->ygrid_scale.labfact = im->ylabfact;
     }
-    
-   X0=im->xorigin;
-   X1=im->xorigin+im->xsize;
+    return 1;
+}
+
+int draw_horizontal_grid(image_desc_t *im)
+{
+    int      i;
+    double   scaledstep;
+    char     graph_label[100];
+    double X0=im->xorigin;
+    double X1=im->xorigin+im->xsize;
    
-    sgrid = (int)( im->minval / gridstep - 1);
-    egrid = (int)( im->maxval / gridstep + 1);
-    scaledstep = gridstep/im->magfact;
+    int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
+    int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
+    scaledstep = im->ygrid_scale.gridstep/im->magfact;
     for (i = sgrid; i <= egrid; i++){
-       Y0=ytr(im,gridstep*i);
+       double Y0=ytr(im,im->ygrid_scale.gridstep*i);
        if ( Y0 >= im->yorigin-im->ysize
                 && Y0 <= im->yorigin){       
-           if(i % labfact == 0){               
+           if(i % im->ygrid_scale.labfact == 0){               
                if (i==0 || im->symbol == ' ') {
                    if(scaledstep < 1){
                        if(im->extra_flags & ALTYGRID) {
-                           sprintf(graph_label,labfmt,scaledstep*i);
+                           sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
                        }
                        else {
                            sprintf(graph_label,"%4.1f",scaledstep*i);
@@ -1502,16 +1581,18 @@ horizontal_grid(image_desc_t   *im)
                              im->text_prop[TEXT_PROP_AXIS].size,
                              im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
                              graph_label );
-              gfx_new_line ( im->canvas,
+              gfx_new_dashed_line ( im->canvas,
                              X0-2,Y0,
                              X1+2,Y0,
-                             MGRIDWIDTH, im->graph_col[GRC_MGRID] );          
+                             MGRIDWIDTH, im->graph_col[GRC_MGRID],
+                             im->grid_dash_on, im->grid_dash_off);            
               
            } else {            
-              gfx_new_line ( im->canvas,
+              gfx_new_dashed_line ( im->canvas,
                              X0-1,Y0,
                              X1+1,Y0,
-                             GRIDWIDTH, im->graph_col[GRC_GRID] );            
+                             GRIDWIDTH, im->graph_col[GRC_GRID],
+                             im->grid_dash_on, im->grid_dash_off);            
               
            }       
        }       
@@ -1562,10 +1643,11 @@ horizontal_log_grid(image_desc_t   *im)
        while(yloglab[minoridx][++i] > 0){          
           Y0 = ytr(im,value * yloglab[minoridx][i]);
           if (Y0 <= im->yorigin - im->ysize) break;
-          gfx_new_line ( im->canvas,
+          gfx_new_dashed_line ( im->canvas,
                          X0-1,Y0,
                          X1+1,Y0,
-                         GRIDWIDTH, im->graph_col[GRC_GRID] );
+                         GRIDWIDTH, im->graph_col[GRC_GRID],
+                         im->grid_dash_on, im->grid_dash_off);
        }
     }
 
@@ -1579,10 +1661,11 @@ horizontal_log_grid(image_desc_t   *im)
        while(yloglab[majoridx][++i] > 0){          
           Y0 = ytr(im,value * yloglab[majoridx][i]);    
           if (Y0 <= im->yorigin - im->ysize) break;
-          gfx_new_line ( im->canvas,
+          gfx_new_dashed_line ( im->canvas,
                          X0-2,Y0,
                          X1+2,Y0,
-                         MGRIDWIDTH, im->graph_col[GRC_MGRID] );
+                         MGRIDWIDTH, im->graph_col[GRC_MGRID],
+                         im->grid_dash_on, im->grid_dash_off);
           
           sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
           gfx_new_text ( im->canvas,
@@ -1603,7 +1686,7 @@ vertical_grid(
     image_desc_t   *im )
 {   
     int xlab_sel;              /* which sort of label and grid ? */
-    time_t ti, tilab;
+    time_t ti, tilab, timajor;
     long factor;
     char graph_label[100];
     double X0,Y0,Y1; /* points for filled graph and more*/
@@ -1636,14 +1719,24 @@ vertical_grid(
     /* paint the minor grid */
     for(ti = find_first_time(im->start,
                            im->xlab_user.gridtm,
-                           im->xlab_user.gridst);
+                           im->xlab_user.gridst),
+        timajor = find_first_time(im->start,
+                           im->xlab_user.mgridtm,
+                           im->xlab_user.mgridst);
        ti < im->end; 
        ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
        ){
        /* are we inside the graph ? */
        if (ti < im->start || ti > im->end) continue;
+       while (timajor < ti) {
+           timajor = find_next_time(timajor,
+                   im->xlab_user.mgridtm, im->xlab_user.mgridst);
+       }
+       if (ti == timajor) continue; /* skip as falls on major grid line */
        X0 = xtr(im,ti);       
-       gfx_new_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH, im->graph_col[GRC_GRID]);
+       gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
+          im->graph_col[GRC_GRID],
+          im->grid_dash_on, im->grid_dash_off);
        
     }
 
@@ -1657,7 +1750,9 @@ vertical_grid(
        /* are we inside the graph ? */
        if (ti < im->start || ti > im->end) continue;
        X0 = xtr(im,ti);
-       gfx_new_line(im->canvas,X0,Y0+2, X0,Y1-2,MGRIDWIDTH, im->graph_col[GRC_MGRID]);
+       gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
+          im->graph_col[GRC_MGRID],
+          im->grid_dash_on, im->grid_dash_off);
        
     }
     /* paint the labels below the graph */
@@ -1757,7 +1852,7 @@ grid_paint(image_desc_t   *im)
        if(im->logarithmic){
                res = horizontal_log_grid(im);
        } else {
-               res = horizontal_grid(im);
+               res = draw_horizontal_grid(im);
        }
 
        /* dont draw horizontal grid if there is no min and max val */
@@ -2135,6 +2230,11 @@ graph_paint(image_desc_t *im, char ***calcpr)
     expand_range(im);   /* make sure the upper and lower limit are
                            sensible values */
 
+  if (!calc_horizontal_grid(im))
+    return -1;
+  if (im->gridfit)
+    apply_gridfit(im);
+
 /**************************************************************
  *** Calculating sizes and locations became a bit confusing ***
  *** so I moved this into a separate function.              ***
@@ -2184,6 +2284,7 @@ graph_paint(image_desc_t *im, char ***calcpr)
     case GF_COMMENT:
     case GF_HRULE:
     case GF_VRULE:
+    case GF_XPORT:
       break;
     case GF_TICK:
       for (ii = 0; ii < im->xsize; ii++)
@@ -2428,7 +2529,6 @@ scan_for_col(char *input, int len, char *output)
     output[outp] = '\0';
     return inp;
 }
-
 /* Some surgery done on this function, it became ridiculously big.
 ** Things moved:
 ** - initializing     now in rrd_graph_init()
@@ -2444,7 +2544,7 @@ rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
     tzset();
 #endif
 #ifdef HAVE_SETLOCALE
-    setlocale(LC_ALL,"");
+    setlocale(LC_TIME,"");
 #endif
             
             
@@ -2521,6 +2621,7 @@ rrd_graph_init(image_desc_t *im)
     im->unitsexponent= 9999;
     im->extra_flags= 0;
     im->rigid = 0;
+    im->gridfit = 1;
     im->imginfo = NULL;
     im->lazy = 0;
     im->logarithmic = 0;
@@ -2532,6 +2633,8 @@ rrd_graph_init(image_desc_t *im)
     im->gdes_c = 0;
     im->gdes = NULL;
     im->canvas = gfx_new_canvas();
+    im->grid_dash_on = 1;
+    im->grid_dash_off = 1;
 
     for(i=0;i<DIM(graph_col);i++)
         im->graph_col[i]=graph_col[i];
@@ -2585,6 +2688,7 @@ rrd_graph_options(int argc, char *argv[],image_desc_t *im)
            {"alt-autoscale-max", no_argument,    0,   259 },
            {"units-exponent",required_argument, 0,  260},
            {"step",       required_argument, 0,   261},
+           {"no-gridfit", no_argument,       0,   262},
            {0,0,0,0}};
        int option_index = 0;
        int opt;
@@ -2616,6 +2720,9 @@ rrd_graph_options(int argc, char *argv[],image_desc_t *im)
        case 261:
            im->step =  atoi(optarg);
            break;
+       case 262:
+           im->gridfit = 0;
+           break;
        case 's':
            if ((parsetime_error = parsetime(optarg, &start_tv))) {
                rrd_set_error( "start time: %s", parsetime_error );
@@ -2848,31 +2955,285 @@ rrd_graph_options(int argc, char *argv[],image_desc_t *im)
     im->end = end_tmp;
 }
 
+/* rrd_name_or_num()
+**
+** Scans for a VDEF-variable or a number
+**
+** Returns an integer describing what was found:
+**
+** 0: error
+** 1: found an integer; it is returned in both l and d
+** 2: found a float; it is returned in d
+** 3: found a vname; its index is returned in l
+**
+** l and d are undefined unless described above
+*/
+static int
+rrd_name_or_num(image_desc_t *im, char *param, long *l, double *d)
+{
+    int i1=0,i2=0,i3=0,i4=0,i5=0,i6=0;
+    char vname[MAX_VNAME_LEN+1];
+
+    sscanf(param, "%li%n%*s%n", l,&i1,&i2);
+    sscanf(param, "%lf%n%*s%n", d,&i3,&i4);
+    sscanf(param, DEF_NAM_FMT "%n%*s%n", vname, &i5,&i6);
+
+    if ( (i1) && (!i2) ) return 1;
+    if ( (i3) && (!i4) ) return 2;
+    if ( (i5) && (!i6) ) {
+       if ((*l = find_var(im,vname))!=-1) return 3;
+    }
+    return 0;
+}
+
+/* rrd_vname_color()
+**
+** Parses "[<vname|number>][#color]" where at least one
+** of the optional strings must exist.
+**
+** Returns an integer describing what was found.
+** If the result is 0, the rrd_error string may be set.
+**
+** ...CVVVV 
+** ---------:-----------------------------------
+** 00000000 : error
+** ....0000 : a value/variable was not found
+** ....0001 : an integer number was found, returned in both l and d
+** ....0010 : a floating point number was found, returned in d
+** ....0011 : reserved for future values
+** ....01xx : reserved for future values
+** ....1000 : an existing  DEF vname was found, idx returned in l
+** ....1001 : an existing CDEF vname was found, idx returned in l
+** ....1010 : an existing VDEF vname was found, idx returned in l
+** ....1011 : reserved for future variables
+** ....11xx : reserved for future variables
+** ...0.... : a color was not found, returned in color
+** ...1.... : a color was found, returned in color
+*/
+static int
+rrd_vname_color(image_desc_t *im, char * param,
+                       long *l,
+                       double *d,
+                       gfx_color_t *color)
+{
+    int result=0,i=0;
+
+    if (param[0]!='#') { /* vname or num present or empty string */
+       char *s,*c=param;
+       while ((*c!='\0')&&(*c!='#')) c++,i++;
+       if (*c!='\0') {
+           s=malloc(i+1);
+           if (s==NULL) {
+               rrd_set_error("Out of memory in function rrd_vname_color");
+               return 0;
+           }
+           strncpy(s,param,i);
+           s[i]='\0';
+           result=rrd_name_or_num(im, s, l, d);
+           if (!result) {
+               rrd_set_error("Use of uninitialized vname %s",s);
+               free(s);
+           }
+       } else {
+           result=rrd_name_or_num(im, param, l, d);
+           if (!result) {
+               rrd_set_error("Use of uninitialized vname %s",param);
+           }
+       }
+       switch (result) {
+           case 0: return 0; /* error set above */
+           case 1:
+           case 2: break;
+           case 3:
+               switch (im->gdes[*l].gf) {
+                   case GF_DEF:        result=0x08;break;
+                   case GF_CDEF:       result=0x09;break;
+                   case GF_VDEF:       result=0x0A;break;
+                   default:
+                       rrd_set_error("Unexpected GF result from function "
+                       "rrd_name_or_num() called from rrd_vname_color");
+                       return 0;
+               }
+               break;
+           default:
+               rrd_set_error("Unexpected result from function "
+                       "rrd_name_or_num() called from rrd_vname_color");
+               return 0;
+       }
+    }
+    /* Parse color, if any. */
+    if (param[i] == '\0') return result;
+    else {
+       unsigned int r=0,g=0,b=0,a=0xFF;
+       int i1=0,i2=0;
+       sscanf(&param[i], "#%02x%02x%02x%n%02x%n",
+                               &r,&g,&b,&i1,&a,&i2);
+       if (!i1) {
+           rrd_set_error("Unparsable color %s",&param[i]);
+           return 0;
+       }
+       if (i2) i1=i2;
+       i2=0;
+       sscanf(&param[i+i1],"%*s%n",&i2);
+       if (i2) {
+           rrd_set_error("Garbage after color %s",param[i]);
+           return 0;
+       }
+       *color=r<<24|g<<16|b<<8|a;
+       return result|0x10;
+    }
+}
+
+/* rrd_find_function()
+**
+** Checks if the parameter is a valid function and
+** if so, returns it in the graph description pointer.
+**
+** The return value is a boolean; true if found
+*/
+static int
+rrd_find_function(char *param, graph_desc_t *gdp)
+{
+    size_t i1=0,i2=0;
+    char funcname[11];
+
+    sscanf(param,"%10[A-Z]%n%*1[1-3]%n",funcname,(int *)&i1,(int *)&i2);
+    gdp->gf=gf_conv(funcname);
+    if ((int)gdp->gf == -1) {
+       rrd_set_error("'%s' is not a valid function name",funcname);
+       return 0;
+    }
+    if (gdp->gf==GF_LINE) {
+       if (i2) {
+           gdp->linewidth=param[i1]-'0';
+       } else {
+           rrd_set_error("LINE should have a width");
+           return 0;
+       }
+    } else {
+       if (i2) {
+           rrd_set_error("Only LINE should have a width: %s",param);
+           return 0;
+       } else {
+           i2=i1;
+       }
+    }
+    if (strlen(param) != i2) {
+       rrd_set_error("Garbage after function name: %s",param);
+       return 0;
+    }
+    return 1;
+}
+/* rrd_split_line()
+**
+** Takes a string as input; splits this line into multiple
+** parameters on each ":" boundary.
+**
+** If this function returns successful, the caller will have
+** to free() the allocated memory for param.
+**
+** The input string is destroyed, its memory is used by the
+** output array.
+*/
+static int
+rrd_split_line(char *line,char ***param)
+{
+    int i=0,n=0;
+    char *c=line;
+
+    /* scan the amount of colons in the line.  We need
+    ** at most this amount+1 pointers for the array. If
+    ** any colons are escaped we waste some space.
+    */
+    if (*c!='\0') n=1;
+    while (*c != '\0')
+       if (*c++ == ':') n++;
+
+    if (n==0) {
+       rrd_set_error("No line to split. rrd_split_line was given the empty string.");
+       return -1;
+    }
+
+    /* Allocate memory for an array of n char pointers */
+    *param=calloc(n,sizeof(char *));
+    if (*param==NULL) {
+       rrd_set_error("Memory allocation failed inside rrd_split_line");
+       return -1;
+    }
+
+    /* split the line and fill the array */
+    c = line;
+    i=0;
+    (*param)[i] = c;
+    while (*c != '\0') {
+       switch (*c) {
+           case '\\':
+               c++;
+               if (*c=='\0') {
+                   free(*param);
+                   rrd_set_error("Lone backslash found inside rrd_split_line");
+                   return -1;
+               }
+               c++;
+               break;
+           case ':':
+               *c = '\0';
+               c++;
+               i++;
+               (*param)[i] = c;
+               break;
+           default:
+               c++;
+       }
+    }
+    i++; /* i separators means i+1 parameters */
+
+    return i;
+}
 void
 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
 {
     int                i;
     char       symname[100];
     int                linepass = 0; /* stack must follow LINE*, AREA or STACK */    
+    char **    param;
+    int                paramcnt,paramused;
 
     for (i=optind+1;i<argc;i++) {
        int             argstart=0;
        int             strstart=0;
        graph_desc_t    *gdp;
        char            *line;
-       char            funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
-       double          d;
-       double          linewidth;
-       int             j,k,l,m;
+       char    tmpline[256];
+       char            vname[MAX_VNAME_LEN+1],sep[1];
+/*     double          d;      */
+       int             j,k,l/*,m*/;
 
-       /* Each command is one element from *argv[], we call this "line".
+       /* Each command is one element from *argv[].  This command is
+       ** split at every unescaped colon.
        **
        ** Each command defines the most current gdes inside struct im.
        ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
        */
        gdes_alloc(im);
        gdp=&im->gdes[im->gdes_c-1];
-       line=argv[i];
+       strcpy(tmpline,argv[i]);
+       line=tmpline;
+       if ((paramcnt=rrd_split_line(argv[i],&param))==-1) return;
+       paramused=0;
+
+#ifdef DEBUG
+       printf("DEBUG: after splitting line:\n");
+       for (j=0;j<paramcnt;j++)
+           printf("DEBUG: %3i: %s\n",j,param[j]);
+#endif
+
+       if (!rrd_find_function(param[paramused],gdp)) {
+           im_free(im);
+           free(param);
+           return;
+       }
+       paramused++;
 
        /* function:newvname=string[:ds-name:CF]        for xDEF
        ** function:vname[#color[:string]]              for LINEx,AREA,STACK
@@ -2881,55 +3242,71 @@ rrd_graph_script(int argc, char *argv[], image_desc_t *im)
        ** function:vname:CF:string                     for xPRINT
        ** function:string                              for COMMENT
        */
-       argstart=0;
 
-       sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
-       if (argstart==0) {
-           rrd_set_error("Cannot parse function in line: %s",line);
-           im_free(im);
-           return;
-       }
-        if(sscanf(funcname,"LINE%lf",&linewidth)){
-                im->gdes[im->gdes_c-1].gf = GF_LINE;
-                im->gdes[im->gdes_c-1].linewidth = linewidth;
-        } else {
-         if ((gdp->gf=gf_conv(funcname))==-1) {
-             rrd_set_error("'%s' is not a valid function name",funcname);
-             im_free(im);
-             return;
-         }
-        }
+/*TEMP*/argstart=strlen(param[paramused-1])+1;
 
-       /* If the error string is set, we exit at the end of the switch */
+       /* If anything fails just use rrd_set_error() and break from the
+       ** switch.  Just after the switch we call rrd_test_error() and
+       ** clean up if it is set.
+       */
        switch (gdp->gf) {
+           case GF_XPORT:
+               break;
            case GF_COMMENT:
-               if (rrd_graph_legend(gdp,&line[argstart])==0)
-                   rrd_set_error("Cannot parse comment in line: %s",line);
+               if (paramcnt<2) {
+                   rrd_set_error("Not enough parameters for %s",param[0]);
+                   break;
+               }
+               if (strlen(param[1])>FMT_LEG_LEN) {
+                   rrd_set_error("Comment too long: %s:%s",param[0],param[1]);
+                   break;
+               }
+               strcpy(gdp->legend,param[1]);
+               paramused++;
                break;
            case GF_PART:
            case GF_VRULE:
            case GF_HRULE:
-               j=k=l=m=0;
-               sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
-               sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
-               if (k+m==0) {
-                   rrd_set_error("Cannot parse name or num in line: %s",line);
+               if (paramcnt<2) {
+                   rrd_set_error("No name or number in %s",param[0]);
                    break;
                }
-               if (j!=0) {
-                   gdp->xrule=d;
-                   gdp->yrule=d;
-                   argstart+=j;
-               } else if (!rrd_graph_check_vname(im,vname,line)) {
-                   gdp->xrule=0;
-                   gdp->yrule=DNAN;
-                   argstart+=l;
-               } else break; /* exit due to wrong vname */
-               if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
-               argstart+=j;
-               if (strlen(&line[argstart])!=0) {
-                   if (rrd_graph_legend(gdp,&line[++argstart])==0)
-                       rrd_set_error("Cannot parse comment in line: %s",line);
+               j=rrd_vname_color(im,param[1],
+                               &gdp->xrule,&gdp->yrule,&gdp->col);
+               paramused++;
+               if (!j) break; /* error string set by function */
+               switch (j&0x0F) {
+                   case 0x00:
+                       rrd_set_error("Cannot parse name nor number "
+                               "in %s:%s",param[0],param[1]);
+                       break;
+                   case 0x08:
+                   case 0x09:
+                       rrd_set_error("Cannot use DEF or CDEF based "
+                               "variable in %s:%s",param[0],param[1]);
+                       break;
+                   case 0x0A:
+                       gdp->vidx=gdp->xrule;
+                       gdp->xrule=0;
+                       gdp->yrule=DNAN;
+                       break;
+                   case 0x01:
+                   case 0x02:
+                       break;
+                   default:
+                       rrd_set_error("Unexpected result while parsing "
+                               "%s:%s, program error",param[0],param[1]);
+               }
+               if (rrd_test_error()) break;
+
+               if (paramcnt>paramused) {
+                   if (strlen(param[paramused])>FMT_LEG_LEN) {
+                       rrd_set_error("Comment too long: %s:%s",
+                                               param[0],param[1]);
+                       break;
+                   }
+                   strcpy(gdp->legend,param[paramused]);
+                   paramused++;
                }
                break;
            case GF_STACK:
@@ -3005,29 +3382,73 @@ rrd_graph_script(int argc, char *argv[], image_desc_t *im)
            case GF_VDEF:
            case GF_CDEF:
                j=0;
-               sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
+               if (paramcnt<2) {
+                   rrd_set_error("Nothing following %s",param[0]);
+                   break;
+               }
+               sscanf(param[1], DEF_NAM_FMT "=%n",gdp->vname,&j);
                if (j==0) {
-                   rrd_set_error("Could not parse line: %s",line);
+                   rrd_set_error("Could not parse %s:%s",param[0],param[1]);
                    break;
                }
                if (find_var(im,gdp->vname)!=-1) {
-                   rrd_set_error("Variable '%s' in line '%s' already in use\n",
-                                                       gdp->vname,line);
+                   rrd_set_error("Variable '%s' in %s:%s' already in use\n",
+                                               gdp->vname,param[0],param[1]);
                    break;
                }
+               paramused++;
                argstart+=j;
                switch (gdp->gf) {
                    case GF_DEF:
-                       argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
+                       if (strlen(&param[1][j])>MAXPATH) {
+                           rrd_set_error("Path too long: %s:%s",param[0],param[1]);
+                           break;
+                       }
+                       strcpy(gdp->rrd,&param[1][j]);
+
+                       if (paramcnt<3) {
+                           rrd_set_error("No DS for %s:%s",param[0],param[1]);
+                           break;
+                       }
                        j=k=0;
-                       sscanf(&line[argstart],
-                               ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
-                               gdp->ds_nam, symname, &j, &k);
+                       sscanf(param[2],DS_NAM_FMT "%n%*s%n",gdp->ds_nam,&j,&k);
                        if ((j==0)||(k!=0)) {
-                           rrd_set_error("Cannot parse DS or CF in '%s'",line);
+                           rrd_set_error("Cannot parse DS in %s:%s:%s",
+                                               param[0],param[1],param[2]);
                            break;
                        }
-                       rrd_graph_check_CF(im,symname,line);
+                       paramused++;
+                       if (paramcnt<4) {
+                           rrd_set_error("No CF for %s:%s:%s",
+                                       param[0],param[1],param[2]);
+                           break;
+                       }
+                       j=k=0;
+                       sscanf(param[3],CF_NAM_FMT "%n%*s%n",symname,&j,&k);
+                       if ((j==0)||(k!=0)) {
+                           rrd_set_error("Cannot parse CF in %s:%s:%s:%s",
+                                       param[0],param[1],param[2],param[3]);
+                           break;
+                       }
+                       if ((gdp->cf = cf_conv(symname))==-1) {
+                           rrd_set_error("Unknown CF '%s' in %s:%s:%s:%s",
+                                       param[0],param[1],param[2],param[3]);
+                           break;
+                       }
+                       paramused++;
+                       if (paramcnt>paramused) {
+                           k=0;l=0;
+                           sscanf(param[4],
+                                       "step=%lu%n%*s%n",
+                                &gdp->step,&k,&l);
+                            if ((k==0)||(l!=0)) {
+                                rrd_set_error("Cannot parse step in "
+                                                       "%s:%s:%s:%s:%s",
+                               param[0],param[1],param[2],param[3],param[4]);
+                               break;
+                            }
+                           paramused++;
+                       }
                        break;
                    case GF_VDEF:
                        j=0;
@@ -3075,6 +3496,7 @@ rrd_graph_script(int argc, char *argv[], image_desc_t *im)
        return; 
     }
 }
+
 int
 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
 {
@@ -3146,33 +3568,41 @@ rrd_graph_legend(graph_desc_t *gdp, char *line)
 
 
 int bad_format(char *fmt) {
-       char *ptr;
-       int n=0;
-
-       ptr = fmt;
-       while (*ptr != '\0') {
-               if (*ptr == '%') {ptr++;
-                       if (*ptr == '\0') return 1;
-                       while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') { 
-                               ptr++;
-                       }
-                       if (*ptr == '\0') return 1;
-                       if (*ptr == 'l') {
-                               ptr++;
-                               n++;
-                               if (*ptr == '\0') return 1;
-                               if (*ptr == 'e' || *ptr == 'f') { 
-                                       ptr++; 
-                                       } else { return 1; }
-                       }
-                       else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
-                       else { return 1; }
-               } else {
-                       ++ptr;
-               }
-       }
-       return (n!=1);
+    char *ptr;
+    int n=0;
+    ptr = fmt;
+    while (*ptr != '\0')
+        if (*ptr++ == '%') {
+             /* line cannot end with percent char */
+             if (*ptr == '\0') return 1;
+             /* '%s', '%S' and '%%' are allowed */
+             if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
+
+             /* or else '% 6.2lf' and such are allowed */
+             else {
+   
+                 /* optional padding character */
+                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
+  
+                 /* This should take care of 'm.n' with all three optional */
+                 while (*ptr >= '0' && *ptr <= '9') ptr++;
+                 if (*ptr == '.') ptr++;
+                 while (*ptr >= '0' && *ptr <= '9') ptr++;
+  
+                 /* Either 'le' or 'lf' must follow here */
+                 if (*ptr++ != 'l') return 1;
+                 if (*ptr == 'e' || *ptr == 'f') ptr++;
+                 else return 1;
+                 n++;
+            }
+         }
+      
+      return (n!=1); 
 }
+
+
 int
 vdef_parse(gdes,str)
 struct graph_desc_t *gdes;
@@ -3259,6 +3689,8 @@ char *str;
     };
     return 0;
 }
+
+
 int
 vdef_calc(im,gdi)
 image_desc_t *im;