prepare for the release of rrdtool-1.2.18
[rrdtool.git] / src / rrd_graph.c
index 95ebfe7..6df9b09 100644 (file)
@@ -1,5 +1,5 @@
 /****************************************************************************
- * RRDtool 1.2.6  Copyright by Tobi Oetiker, 1997-2005
+ * RRDtool 1.2.18  Copyright by Tobi Oetiker, 1997-2006
  ****************************************************************************
  * rrd__graph.c  produce graphs from data in rrdfiles
  ****************************************************************************/
@@ -7,9 +7,12 @@
 
 #include <sys/stat.h>
 
+#ifdef WIN32
+#include "strftime.h"
+#endif
 #include "rrd_tool.h"
 
-#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
+#if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
 #include <io.h>
 #include <fcntl.h>
 #endif
@@ -42,37 +45,29 @@ text_prop_t text_prop[] = {
 };
 
 xlab_t xlab[] = {
-    {0,        TMT_SECOND,30, TMT_MINUTE,5,  TMT_MINUTE,5,         0,"%H:%M"},
-    {2,        TMT_MINUTE,1,  TMT_MINUTE,5,  TMT_MINUTE,5,         0,"%H:%M"},
-    {5,        TMT_MINUTE,2,  TMT_MINUTE,10, TMT_MINUTE,10,        0,"%H:%M"},
-    {10,       TMT_MINUTE,5,  TMT_MINUTE,20, TMT_MINUTE,20,        0,"%H:%M"},
-    {30,       TMT_MINUTE,10, TMT_HOUR,1,    TMT_HOUR,1,           0,"%H:%M"},
-    {60,       TMT_MINUTE,30, TMT_HOUR,2,    TMT_HOUR,2,           0,"%H:%M"},
-    {180,      TMT_HOUR,1,    TMT_HOUR,6,    TMT_HOUR,6,           0,"%H:%M"},
-    /*{300,      TMT_HOUR,3,    TMT_HOUR,12,   TMT_HOUR,12,    12*3600,"%a %p"},  this looks silly*/
-    {600,      TMT_HOUR,6,    TMT_DAY,1,     TMT_DAY,1,      24*3600,"%a"},
-    {1800,     TMT_HOUR,12,   TMT_DAY,1,     TMT_DAY,2,      24*3600,"%a"},
-    {3600,     TMT_DAY,1,     TMT_WEEK,1,     TMT_WEEK,1,    7*24*3600,"Week %V"},
-    {3*3600,   TMT_WEEK,1,      TMT_MONTH,1,     TMT_WEEK,2,    7*24*3600,"Week %V"},
-    {6*3600,   TMT_MONTH,1,   TMT_MONTH,1,   TMT_MONTH,1, 30*24*3600,"%b"},
-    {48*3600,  TMT_MONTH,1,   TMT_MONTH,3,   TMT_MONTH,3, 30*24*3600,"%b"},
-    {10*24*3600, TMT_YEAR,1,  TMT_YEAR,1,    TMT_YEAR,1, 365*24*3600,"%y"},
-    {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
+    {0,                 0,   TMT_SECOND,30, TMT_MINUTE,5,  TMT_MINUTE,5,         0,"%H:%M"},
+    {2,                 0,   TMT_MINUTE,1,  TMT_MINUTE,5,  TMT_MINUTE,5,         0,"%H:%M"},
+    {5,                 0,   TMT_MINUTE,2,  TMT_MINUTE,10, TMT_MINUTE,10,        0,"%H:%M"},
+    {10,                0,   TMT_MINUTE,5,  TMT_MINUTE,20, TMT_MINUTE,20,        0,"%H:%M"},
+    {30,                0,   TMT_MINUTE,10, TMT_HOUR,1,    TMT_HOUR,1,           0,"%H:%M"},
+    {60,                0,   TMT_MINUTE,30, TMT_HOUR,2,    TMT_HOUR,2,           0,"%H:%M"},
+    {60,          24*3600,   TMT_MINUTE,30, TMT_HOUR,2,    TMT_HOUR,4,           0,"%a %H:%M"},
+    {180,               0,   TMT_HOUR,1,    TMT_HOUR,6,    TMT_HOUR,6,           0,"%H:%M"},
+    {180,         24*3600,   TMT_HOUR,1,    TMT_HOUR,6,    TMT_HOUR,12,          0,"%a %H:%M"},
+    /*{300,             0,   TMT_HOUR,3,    TMT_HOUR,12,   TMT_HOUR,12,    12*3600,"%a %p"},  this looks silly*/
+    {600,               0,   TMT_HOUR,6,    TMT_DAY,1,     TMT_DAY,1,      24*3600,"%a"},
+    {1200,             0,   TMT_HOUR,6,    TMT_DAY,1,     TMT_DAY,1,      24*3600,"%d"},
+    {1800,              0,   TMT_HOUR,12,   TMT_DAY,1,     TMT_DAY,2,      24*3600,"%a %d"},
+    {2400,              0,   TMT_HOUR,12,   TMT_DAY,1,     TMT_DAY,2,      24*3600,"%a"},
+    {3600,              0,   TMT_DAY,1,     TMT_WEEK,1,    TMT_WEEK,1,   7*24*3600,"Week %V"},
+    {3*3600,            0,   TMT_WEEK,1,    TMT_MONTH,1,   TMT_WEEK,2,   7*24*3600,"Week %V"},
+    {6*3600,            0,   TMT_MONTH,1,   TMT_MONTH,1,   TMT_MONTH,1, 30*24*3600,"%b"},
+    {48*3600,           0,   TMT_MONTH,1,   TMT_MONTH,3,   TMT_MONTH,3, 30*24*3600,"%b"},
+    {315360,            0,   TMT_MONTH,3,   TMT_YEAR,1,    TMT_YEAR,1,  365*24*3600,"%Y"},
+    {10*24*3600,        0,   TMT_YEAR,1,  TMT_YEAR,1,    TMT_YEAR,1, 365*24*3600,"%y"},
+    {-1,0,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
 };
 
-/* sensible logarithmic y label intervals ...
-   the first element of each row defines the possible starting points on the
-   y axis ... the other specify the */
-
-double yloglab[][12]= {{ 1e9, 1,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0 },
-                      {  1e3, 1,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0 },
-                      {  1e1, 1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
-                      /* {  1e1, 1,  5,  0,  0,  0,  0,  0,  0,  0,  0,  0 }, */
-                      {  1e1, 1,  2.5,  5,  7.5,  0,  0,  0,  0,  0,  0,  0 },
-                      {  1e1, 1,  2,  4,  6,  8,  0,  0,  0,  0,  0,  0 },
-                      {  1e1, 1,  2,  3,  4,  5,  6,  7,  8,  9,  0,  0 },
-                      {  0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 }};
-
 /* sensible y label intervals ...*/
 
 ylab_t ylab[]= {
@@ -176,7 +171,7 @@ enum gf_en gf_conv(char *string){
     conv_if(VRULE,GF_VRULE)
     conv_if(LINE,GF_LINE)
     conv_if(AREA,GF_AREA)
-    conv_if(STACK,GF_STACK)
+    conv_if(STACK,GF_STACK) 
     conv_if(TICK,GF_TICK)
     conv_if(DEF,GF_DEF)
     conv_if(CDEF,GF_CDEF)
@@ -309,14 +304,8 @@ auto_scale(
 }
 
 
-/* find SI magnitude symbol for the numbers on the y-axis*/
-void 
-si_unit(
-    image_desc_t *im   /* image description */
-)
-{
-
-    char symbol[] = {'a', /* 10e-18 Atto */ 
+static char si_symbol[] = {
+                    'a', /* 10e-18 Atto */ 
                     'f', /* 10e-15 Femto */
                     'p', /* 10e-12 Pico */
                     'n', /* 10e-9  Nano */
@@ -328,9 +317,17 @@ si_unit(
                     'G', /* 10e9   Giga */
                     'T', /* 10e12  Tera */
                     'P', /* 10e15  Peta */
-                    'E'};/* 10e18  Exa */
+                    'E', /* 10e18  Exa */
+};
+static const int si_symbcenter = 6;
+
+/* find SI magnitude symbol for the numbers on the y-axis*/
+void 
+si_unit(
+    image_desc_t *im   /* image description */
+)
+{
 
-    int   symbcenter = 6;
     double digits,viewdigits=0;  
     
     digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base)); 
@@ -350,11 +347,9 @@ si_unit(
 
     im->viewfactor = im->magfact / pow((double)im->base , viewdigits);
 
-    pow((double)im->base , viewdigits);
-
-    if ( ((viewdigits+symbcenter) < sizeof(symbol)) &&
-                   ((viewdigits+symbcenter) >= 0) )
-        im->symbol = symbol[(int)viewdigits+symbcenter];
+    if ( ((viewdigits+si_symbcenter) < sizeof(si_symbol)) &&
+                   ((viewdigits+si_symbcenter) >= 0) )
+        im->symbol = si_symbol[(int)viewdigits+si_symbcenter];
     else
        im->symbol = '?';
  }
@@ -394,7 +389,7 @@ expand_range(image_desc_t *im)
              delt = im->maxval - im->minval;
              adj = delt * 0.1;
              fact = 2.0 * pow(10.0,
-                   floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
+                   floor(log10(max(fabs(im->minval), fabs(im->maxval))/im->magfact)) - 2);
              if (delt < fact) {
                adj = (fact - delt) * 0.55;
 #ifdef DEBUG
@@ -432,10 +427,6 @@ expand_range(image_desc_t *im)
                    -sensiblevalues[i] >=scaled_max)
                    im->maxval = -sensiblevalues[i]*(im->magfact);
            }
-           /* no sensiblevalues found. we switch to ALTYGRID mode */
-           if (sensiblevalues[i] == 0){
-               im->extra_flags |= ALTYGRID;
-           }           
        }
     } else {
        /* adjust min and max to the grid definition if there is one */
@@ -475,7 +466,7 @@ apply_gridfit(image_desc_t *im)
       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 */
+      ytr(im,DNAN); /* reset precalc */
       log10_range = log10(im->maxval) - log10(im->minval);
     }
     /* make sure first y=10^x gridline is located on 
@@ -487,7 +478,7 @@ apply_gridfit(image_desc_t *im)
       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 */
+      ytr(im,DNAN); /* reset precalc */
     }
   } else {
     /* Make sure we have an integer pixel distance between
@@ -500,7 +491,7 @@ apply_gridfit(image_desc_t *im)
     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 */
+    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)
@@ -512,7 +503,7 @@ apply_gridfit(image_desc_t *im)
       double range = im->maxval - im->minval;
       im->minval = im->minval - yfrac * range;
       im->maxval = im->maxval - yfrac * range;
-      ytr(im, DNAN); /* reset precalc */
+      ytr(im,DNAN); /* reset precalc */
     }
     calc_horizontal_grid(im); /* recalc with changed im->maxval */
   }
@@ -694,7 +685,7 @@ data_fetch(image_desc_t *im )
     int i,ii;
     int                skip;
 
-    /* pull the data from the log files ... */
+    /* pull the data from the rrd files ... */
     for (i=0;i< (int)im->gdes_c;i++){
        /* only GF_DEF elements fetch data */
        if (im->gdes[i].gf != GF_DEF) 
@@ -708,9 +699,9 @@ data_fetch(image_desc_t *im )
            if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
                        && (im->gdes[i].cf    == im->gdes[ii].cf)
                        && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
-                       && (im->gdes[i].start == im->gdes[ii].start)
-                       && (im->gdes[i].end   == im->gdes[ii].end)
-                       && (im->gdes[i].step  == im->gdes[ii].step)) {
+                       && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
+                       && (im->gdes[i].end_orig   == im->gdes[ii].end_orig)
+                       && (im->gdes[i].step_orig  == im->gdes[ii].step_orig)) {
                /* OK, the data is already there.
                ** Just copy the header portion
                */
@@ -727,7 +718,7 @@ data_fetch(image_desc_t *im )
                break;
        }
        if (! skip) {
-           unsigned long  ft_step = im->gdes[i].step ;
+           unsigned long  ft_step = im->gdes[i].step ; /* ft_step will record what we got from fetch */
            
            if((rrd_fetch_fn(im->gdes[i].rrd,
                             im->gdes[i].cf,
@@ -740,7 +731,6 @@ data_fetch(image_desc_t *im )
                return -1;
            }
            im->gdes[i].data_first = 1;     
-           im->gdes[i].step = im->step;
        
            if (ft_step < im->gdes[i].step) {
                reduce_data(im->gdes[i].cf_reduce,
@@ -1023,8 +1013,7 @@ data_proc( image_desc_t *im ){
     for(i=0;i<im->gdes_c;i++) {
        if((im->gdes[i].gf==GF_LINE) ||
                (im->gdes[i].gf==GF_AREA) ||
-               (im->gdes[i].gf==GF_TICK) ||
-               (im->gdes[i].gf==GF_STACK)) {
+               (im->gdes[i].gf==GF_TICK)) {
            if((im->gdes[i].p_data = malloc((im->xsize +1)
                                        * sizeof(rrd_value_t)))==NULL){
                rrd_set_error("malloc data_proc");
@@ -1046,7 +1035,6 @@ data_proc( image_desc_t *im ){
                case GF_TICK:
                    if (!im->gdes[ii].stack)
                        paintval = 0.0;
-               case GF_STACK:
                    value = im->gdes[ii].yrule;
                    if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
                        /* The time of the data doesn't necessarily match
@@ -1076,8 +1064,9 @@ data_proc( image_desc_t *im ){
                        ** relevant for min and max
                        */
                        if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
-                           if (isnan(minval) || paintval <  minval)
-                               minval = paintval;
+                           if ((isnan(minval) || paintval <  minval ) &&
+                             ! (im->logarithmic && paintval <= 0.0)) 
+                                       minval = paintval;
                            if (isnan(maxval) || paintval >  maxval)
                                maxval = paintval;
                        }
@@ -1085,6 +1074,10 @@ data_proc( image_desc_t *im ){
                        im->gdes[ii].p_data[i] = DNAN;
                    }
                    break;
+               case GF_STACK:
+                    rrd_set_error("STACK should already be turned into LINE or AREA here");
+                    return -1;
+                    break;
                default:
                    break;
            }
@@ -1095,20 +1088,30 @@ data_proc( image_desc_t *im ){
        there was no data in the graph ... this is not good ...
        lets set these to dummy values then ... */
 
-    if (isnan(minval)) minval = 0.0;
-    if (isnan(maxval)) maxval = 1.0;
+    if (im->logarithmic) {
+       if (isnan(minval)) minval = 0.2;
+       if (isnan(maxval)) maxval = 5.1;
+    }
+    else {
+       if (isnan(minval)) minval = 0.0;
+       if (isnan(maxval)) maxval = 1.0;
+    }
     
     /* adjust min and max values */
     if (isnan(im->minval) 
-       /* don't adjust low-end with log scale */
-       || ((!im->logarithmic && !im->rigid) && im->minval > minval)
-       )
-       im->minval = minval;
+       /* don't adjust low-end with log scale */ /* why not? */
+       || ((!im->rigid) && im->minval > minval)
+       ) {
+       if (im->logarithmic)
+           im->minval = minval * 0.5;
+       else
+           im->minval = minval;
+    }
     if (isnan(im->maxval) 
        || (!im->rigid && im->maxval < maxval)
        ) {
        if (im->logarithmic)
-           im->maxval = maxval * 1.1;
+           im->maxval = maxval * 2.0;
        else
            im->maxval = maxval;
     }
@@ -1229,7 +1232,7 @@ print_calc(image_desc_t *im, char ***prdata)
 {
     long i,ii,validsteps;
     double printval;
-    time_t printtime;
+    struct tm tmvdef;
     int graphelement = 0;
     long vidx;
     int max_ii;        
@@ -1237,6 +1240,9 @@ print_calc(image_desc_t *im, char ***prdata)
     char *si_symb = "";
     char *percent_s;
     int prlines = 1;
+    /* wow initializing tmvdef is quite a task :-) */
+    time_t now = time(NULL);
+    localtime_r(&now,&tmvdef);
     if (im->imginfo) prlines++;
     for(i=0;i<im->gdes_c;i++){
        switch(im->gdes[i].gf){
@@ -1254,7 +1260,7 @@ print_calc(image_desc_t *im, char ***prdata)
            vidx = im->gdes[i].vidx;
            if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
                printval = im->gdes[vidx].vf.val;
-               printtime = im->gdes[vidx].vf.when;
+               localtime_r(&im->gdes[vidx].vf.when,&tmvdef);
            } else { /* need to calculate max,min,avg etcetera */
                max_ii =((im->gdes[vidx].end 
                        - im->gdes[vidx].start)
@@ -1300,21 +1306,6 @@ print_calc(image_desc_t *im, char ***prdata)
                }
            } /* prepare printval */
 
-           if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
-               char ctime_buf[128]; /* PS: for ctime_r, must be >= 26 chars */
-               int iii=0;
-               ctime_r(&printtime,ctime_buf); 
-               while(isprint(ctime_buf[iii])){iii++;}
-               ctime_buf[iii]='\0';
-               if (im->gdes[i].gf == GF_PRINT){
-                   (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
-                   sprintf((*prdata)[prlines-2],"%s (%lu)",ctime_buf,printtime);
-                   (*prdata)[prlines-1] = NULL;
-               } else {
-                   sprintf(im->gdes[i].legend,"%s (%lu)",ctime_buf,printtime);
-                   graphelement = 1;
-               }
-           } else {
            if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
                /* Magfact is set to -1 upon entry to print_calc.  If it
                 * is still less than 0, then we need to run auto_scale.
@@ -1333,40 +1324,57 @@ print_calc(image_desc_t *im, char ***prdata)
                auto_scale(im,&printval,&si_symb,&magfact);
            }
 
-           if (im->gdes[i].gf == GF_PRINT){
+            if (im->gdes[i].gf == GF_PRINT){
                (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
                (*prdata)[prlines-1] = NULL;
-               if (bad_format(im->gdes[i].format)) {
-                       rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
+                if (im->gdes[i].strftm){
+                       strftime((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
+                } else {
+                 if (bad_format(im->gdes[i].format)) {
+                       rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
                        return -1;
-               }
+                 }
+
 #ifdef HAVE_SNPRINTF
-               snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
+                 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
 #else
-               sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
+                 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
 #endif
-           } else {
+               }
+             } else {
                /* GF_GPRINT */
 
-               if (bad_format(im->gdes[i].format)) {
+                if (im->gdes[i].strftm){
+                       strftime(im->gdes[i].legend,FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
+                } else {
+                 if (bad_format(im->gdes[i].format)) {
                        rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
                        return -1;
-               }
+                 }
 #ifdef HAVE_SNPRINTF
-               snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
+                 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
 #else
-               sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
+                 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
 #endif
-               graphelement = 1;
-           }
-           }
+                }
+               graphelement = 1;               
+           }       
            break;
        case GF_LINE:
        case GF_AREA:
        case GF_TICK:
-       case GF_STACK:
+           graphelement = 1;
+           break;
        case GF_HRULE:
+            if(isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
+               im->gdes[i].yrule=im->gdes[im->gdes[i].vidx].vf.val;
+            };
+           graphelement = 1;
+           break;
        case GF_VRULE:
+            if(im->gdes[i].xrule == 0) { /* again ... the legend printer needs it*/
+              im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
+            };
            graphelement = 1;
            break;
         case GF_COMMENT:
@@ -1379,6 +1387,10 @@ print_calc(image_desc_t *im, char ***prdata)
        case GF_SHIFT:
        case GF_XPORT:
            break;
+       case GF_STACK:
+            rrd_set_error("STACK should already be turned into LINE or AREA here");
+            return -1;
+            break;
        }
     }
     return graphelement;
@@ -1395,6 +1407,7 @@ leg_place(image_desc_t *im)
     int   fill=0, fill_last;
     int   leg_c = 0;
     int   leg_x = border, leg_y = im->yimg;
+    int   leg_y_prev = im->yimg;
     int   leg_cc;
     int   glue = 0;
     int   i,ii, mark = 0;
@@ -1433,7 +1446,27 @@ leg_place(image_desc_t *im)
        } else {
            prt_fctn = '\0';
        }
+       /* only valid control codes */
+        if (prt_fctn != 'l' && 
+           prt_fctn != 'n' && /* a synonym for l */
+           prt_fctn != 'r' &&
+           prt_fctn != 'j' &&
+           prt_fctn != 'c' &&
+           prt_fctn != 's' &&
+            prt_fctn != 't' &&
+            prt_fctn != '\0' &&
+            prt_fctn != 'g' ) {
+              free(legspace);
+              rrd_set_error("Unknown control code at the end of '%s\\%c'",im->gdes[i].legend,prt_fctn);
+                      return -1;
+
+       }
+
         /* remove exess space */
+        if ( prt_fctn == 'n' ){
+            prt_fctn='l';
+        }
+
         while (prt_fctn=='g' && 
               leg_cc > 0 && 
               im->gdes[i].legend[leg_cc-1]==' '){
@@ -1503,14 +1536,22 @@ leg_place(image_desc_t *im)
                   + legspace[ii]
                   + glue;
            }                   
-           leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
-           if (prt_fctn == 's') leg_y -=  im->text_prop[TEXT_PROP_LEGEND].size;           
+            leg_y_prev = leg_y;
+            /* only add y space if there was text on the line */
+            if (leg_x > border || prt_fctn == 's')            
+              leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
+           if (prt_fctn == 's')
+               leg_y -=  im->text_prop[TEXT_PROP_LEGEND].size;    
            fill = 0;
            leg_c = 0;
            mark = ii;
        }          
     }
-    im->yimg = leg_y;
+    im->yimg = leg_y_prev;
+    /* if we did place some legends we have to add vertical space */
+    if (leg_y != im->yimg){
+       im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
+    }
     free(legspace);
   }
   return 0;
@@ -1530,11 +1571,10 @@ calc_horizontal_grid(image_desc_t   *im)
     double   range;
     double   scaledrange;
     int      pixel,i;
-    int      gridind;
+    int      gridind=0;
     int      decimals, fractionals;
 
     im->ygrid_scale.labfact=2;
-    gridind=-1;
     range =  im->maxval - im->minval;
     scaledrange = range / im->magfact;
 
@@ -1549,21 +1589,12 @@ calc_horizontal_grid(image_desc_t   *im)
     if(isnan(im->ygridstep)){
        if(im->extra_flags & ALTYGRID) {
            /* find the value with max number of digits. Get number of digits */
-           decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
+           decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
            if(decimals <= 0) /* everything is small. make place for zero */
                decimals = 1;
            
-           fractionals = floor(log10(range));
-           if(fractionals < 0) { /* small amplitude. */
-               int len = decimals - fractionals + 1;
-               if (im->unitslength < len) im->unitslength = len;
-               sprintf(im->ygrid_scale.labfmt, "%%%d.%df", len, -fractionals + 1);
-           } else {
-               int len = decimals + 1;
-               if (im->unitslength < len) im->unitslength = len;
-               sprintf(im->ygrid_scale.labfmt, "%%%d.1f", len);
-           }
-           im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
+           im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
+           
            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 */
@@ -1580,21 +1611,30 @@ calc_horizontal_grid(image_desc_t   *im)
                im->ygrid_scale.gridstep /= 5;
                im->ygrid_scale.labfact = 5;
            }
+           fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
+           if(fractionals < 0) { /* small amplitude. */
+               int len = decimals - fractionals + 1;
+               if (im->unitslength < len+2) im->unitslength = len+2;
+               sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
+           } else {
+               int len = decimals + 1;
+               if (im->unitslength < len+2) im->unitslength = len+2;
+               sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
+           }
        }
        else {
            for(i=0;ylab[i].grid > 0;i++){
                pixel = im->ysize / (scaledrange / ylab[i].grid);
-               if (pixel > 7) {
-                   gridind = i;
-                   break;
-               }
+               gridind = i;
+               if (pixel > 7)
+                    break;
            }
            
            for(i=0; i<4;i++) {
               if (pixel * ylab[gridind].lfac[i] >=  2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
-                 im->ygrid_scale.labfact =  ylab[gridind].lfac[i];
+                 im->ygrid_scale.labfact =  ylab[gridind].lfac[i];
                  break;
-              }                          
+               }
            } 
            
            im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
@@ -1611,37 +1651,47 @@ int draw_horizontal_grid(image_desc_t *im)
     int      i;
     double   scaledstep;
     char     graph_label[100];
+    int      nlabels=0;
     double X0=im->xorigin;
     double X1=im->xorigin+im->xsize;
    
     int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
     int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
     double MaxY;
-    scaledstep = im->ygrid_scale.gridstep/im->magfact;
-    MaxY = scaledstep*(double)im->viewfactor*(double)egrid;
+    scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
+    MaxY = scaledstep*(double)egrid;
     for (i = sgrid; i <= egrid; i++){
        double Y0=ytr(im,im->ygrid_scale.gridstep*i);
+       double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
        if ( Y0 >= im->yorigin-im->ysize
                 && Y0 <= im->yorigin){       
-           if(i % im->ygrid_scale.labfact == 0){               
-               if (i==0 || im->symbol == ' ') {
-                   if(MaxY < 10) {
-                       if(im->extra_flags & ALTYGRID) {
-                           sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*im->viewfactor*i);
-                       }
-                       else {
-                           sprintf(graph_label,"%4.1f",scaledstep*im->viewfactor*i);
-                       }
-                   } else {
-                       sprintf(graph_label,"%4.0f",scaledstep*im->viewfactor*i);
-                   }
+           /* Make sure at least 2 grid labels are shown, even if it doesn't agree
+              with the chosen settings. Add a label if required by settings, or if
+              there is only one label so far and the next grid line is out of bounds. */
+           if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){         
+               if (im->symbol == ' ') {
+                   if(im->extra_flags & ALTYGRID) {
+                       sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
+                    } else {
+                        if(MaxY < 10) {
+                          sprintf(graph_label,"%4.1f",scaledstep*(double)i);
+                       } else {
+                          sprintf(graph_label,"%4.0f",scaledstep*(double)i);
+                       }
+                    }
                }else {
-                   if(MaxY < 10){
-                       sprintf(graph_label,"%4.1f %c",scaledstep*im->viewfactor*i, im->symbol);
-                   } else {
-                       sprintf(graph_label,"%4.0f %c",scaledstep*im->viewfactor*i, im->symbol);
-                   }
+                   char sisym = ( i == 0  ? ' ' : im->symbol);
+                   if(im->extra_flags & ALTYGRID) {
+                       sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
+                    } else {
+                       if(MaxY < 10){
+                         sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
+                       } else {
+                         sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
+                       }
+                    }
                }
+               nlabels++;
 
               gfx_new_text ( im->canvas,
                              X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
@@ -1669,84 +1719,260 @@ int draw_horizontal_grid(image_desc_t *im)
     return 1;
 }
 
+/* this is frexp for base 10 */
+double frexp10(double, double *);
+double frexp10(double x, double *e) {
+    double mnt;
+    int iexp;
+
+    iexp = floor(log(fabs(x)) / log(10));
+    mnt = x / pow(10.0, iexp);
+    if(mnt >= 10.0) {
+       iexp++;
+       mnt = x / pow(10.0, iexp);
+    }
+    *e = iexp;
+    return mnt;
+}
+
+static int AlmostEqual2sComplement (float A, float B, int maxUlps)
+{
+
+    int aInt = *(int*)&A;
+    int bInt = *(int*)&B;
+    int intDiff;
+    /* Make sure maxUlps is non-negative and small enough that the
+       default NAN won't compare as equal to anything.  */
+
+    /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
+
+    /* Make aInt lexicographically ordered as a twos-complement int */
+
+    if (aInt < 0)
+        aInt = 0x80000000l - aInt;
+
+    /* Make bInt lexicographically ordered as a twos-complement int */
+
+    if (bInt < 0)
+        bInt = 0x80000000l - bInt;
+
+    intDiff = abs(aInt - bInt);
+
+    if (intDiff <= maxUlps)
+        return 1;
+
+    return 0;
+}
+
 /* logaritmic horizontal grid */
 int
 horizontal_log_grid(image_desc_t   *im)   
 {
-    double   pixpex;
-    int      ii,i;
-    int      minoridx=0, majoridx=0;
-    char     graph_label[100];
-    double   X0,X1,Y0;   
-    double   value, pixperstep, minstep;
+    double yloglab[][10] = {
+       {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
+       {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
+       {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
+       {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
+       {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
+       {0,0,0,0,0, 0,0,0,0,0} /* last line */ };
+
+    int i, j, val_exp, min_exp;
+    double nex;                /* number of decades in data */
+    double logscale;   /* scale in logarithmic space */
+    int exfrac = 1;    /* decade spacing */
+    int mid = -1;      /* row in yloglab for major grid */
+    double mspac;      /* smallest major grid spacing (pixels) */
+    int flab;          /* first value in yloglab to use */
+    double value, tmp, pre_value;
+    double X0,X1,Y0;   
+    char graph_label[100];
 
-    /* find grid spaceing */
-    pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
+    nex = log10(im->maxval / im->minval);
+    logscale = im->ysize / nex;
 
-       if (isnan(pixpex)) {
-               return 0;
-       }
+    /* major spacing for data with high dynamic range */
+    while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
+       if(exfrac == 1) exfrac = 3;
+       else exfrac += 3;
+    }
 
-    for(i=0;yloglab[i][0] > 0;i++){
-       minstep = log10(yloglab[i][0]);
-       for(ii=1;yloglab[i][ii+1] > 0;ii++){
-           if(yloglab[i][ii+2]==0){
-               minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
-               break;
+    /* major spacing for less dynamic data */
+    do {
+       /* search best row in yloglab */
+       mid++;
+       for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
+       mspac = logscale * log10(10.0 / yloglab[mid][i]);
+    } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
+    if(mid) mid--;
+
+    /* find first value in yloglab */
+    for(flab = 0; yloglab[mid][flab] < 10 && frexp10(im->minval, &tmp) > yloglab[mid][flab] ; flab++);
+    if(yloglab[mid][flab] == 10.0) {
+       tmp += 1.0;
+       flab = 0;
+    }
+    val_exp = tmp;
+    if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
+
+    X0=im->xorigin;
+    X1=im->xorigin+im->xsize;
+
+    /* draw grid */
+    pre_value = DNAN;
+    while(1) {       
+
+       value = yloglab[mid][flab] * pow(10.0, val_exp);
+        if (  AlmostEqual2sComplement(value,pre_value,4) ) break; /* it seems we are not converging */
+
+        pre_value = value;
+
+       Y0 = ytr(im, value);
+       if(Y0 <= im->yorigin - im->ysize) break;
+
+       /* major grid line */
+       gfx_new_dashed_line ( im->canvas,
+           X0-2,Y0,
+           X1+2,Y0,
+           MGRIDWIDTH, im->graph_col[GRC_MGRID],
+           im->grid_dash_on, im->grid_dash_off);
+
+       /* label */
+       if (im->extra_flags & FORCE_UNITS_SI) {
+           int scale;
+           double pvalue;
+           char symbol;
+
+           scale = floor(val_exp / 3.0);
+           if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
+           else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
+           pvalue *= yloglab[mid][flab];
+
+           if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
+               ((scale+si_symbcenter) >= 0) )
+               symbol = si_symbol[scale+si_symbcenter];
+           else
+               symbol = '?';
+
+               sprintf(graph_label,"%3.0f %c", pvalue, symbol);
+        } else
+           sprintf(graph_label,"%3.0e", value);
+       gfx_new_text ( im->canvas,
+           X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
+           im->graph_col[GRC_FONT],
+           im->text_prop[TEXT_PROP_AXIS].font,
+           im->text_prop[TEXT_PROP_AXIS].size,
+           im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
+           graph_label );
+
+       /* minor grid */
+       if(mid < 4 && exfrac == 1) {
+           /* find first and last minor line behind current major line
+            * i is the first line and j tha last */
+           if(flab == 0) {
+               min_exp = val_exp - 1;
+               for(i = 1; yloglab[mid][i] < 10.0; i++);
+               i = yloglab[mid][i - 1] + 1;
+               j = 10;
+           }
+           else {
+               min_exp = val_exp;
+               i = yloglab[mid][flab - 1] + 1;
+               j = yloglab[mid][flab];
+           }
+
+           /* draw minor lines below current major line */
+           for(; i < j; i++) {
+
+               value = i * pow(10.0, min_exp);
+               if(value < im->minval) continue;
+
+               Y0 = ytr(im, value);
+               if(Y0 <= im->yorigin - im->ysize) break;
+
+               /* draw lines */
+               gfx_new_dashed_line ( im->canvas,
+                   X0-1,Y0,
+                   X1+1,Y0,
+                   GRIDWIDTH, im->graph_col[GRC_GRID],
+                   im->grid_dash_on, im->grid_dash_off);
            }
        }
-       pixperstep = pixpex * minstep;
-       if(pixperstep > 5){minoridx = i;}
-       if(pixperstep > 2 *  im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
+       else if(exfrac > 1) {
+           for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
+               value = pow(10.0, i);
+               if(value < im->minval) continue;
+
+               Y0 = ytr(im, value);
+               if(Y0 <= im->yorigin - im->ysize) break;
+
+               /* draw lines */
+               gfx_new_dashed_line ( im->canvas,
+                   X0-1,Y0,
+                   X1+1,Y0,
+                   GRIDWIDTH, im->graph_col[GRC_GRID],
+                   im->grid_dash_on, im->grid_dash_off);
+           }
+       }
+
+       /* next decade */
+       if(yloglab[mid][++flab] == 10.0) {
+           flab = 0;
+           val_exp += exfrac;
+       }
     }
-   
-   X0=im->xorigin;
-   X1=im->xorigin+im->xsize;
-    /* paint minor grid */
-    for (value = pow((double)10, log10(im->minval) 
-                         - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
-        value  <= im->maxval;
-        value *= yloglab[minoridx][0]){
-       if (value < im->minval) continue;
-       i=0;    
-       while(yloglab[minoridx][++i] > 0){          
-          Y0 = ytr(im,value * yloglab[minoridx][i]);
-          if (Y0 <= im->yorigin - im->ysize) break;
-          gfx_new_dashed_line ( im->canvas,
-                         X0-1,Y0,
-                         X1+1,Y0,
-                         GRIDWIDTH, im->graph_col[GRC_GRID],
-                         im->grid_dash_on, im->grid_dash_off);
+
+    /* draw minor lines after highest major line */
+    if(mid < 4 && exfrac == 1) {
+       /* find first and last minor line below current major line
+        * i is the first line and j tha last */
+       if(flab == 0) {
+           min_exp = val_exp - 1;
+           for(i = 1; yloglab[mid][i] < 10.0; i++);
+           i = yloglab[mid][i - 1] + 1;
+           j = 10;
+       }
+       else {
+           min_exp = val_exp;
+           i = yloglab[mid][flab - 1] + 1;
+           j = yloglab[mid][flab];
+       }
+
+       /* draw minor lines below current major line */
+       for(; i < j; i++) {
+
+           value = i * pow(10.0, min_exp);
+           if(value < im->minval) continue;
+
+           Y0 = ytr(im, value);
+           if(Y0 <= im->yorigin - im->ysize) break;
+
+           /* draw lines */
+           gfx_new_dashed_line ( im->canvas,
+               X0-1,Y0,
+               X1+1,Y0,
+               GRIDWIDTH, im->graph_col[GRC_GRID],
+               im->grid_dash_on, im->grid_dash_off);
+       }
+    }
+    /* fancy minor gridlines */
+    else if(exfrac > 1) {
+       for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
+           value = pow(10.0, i);
+           if(value < im->minval) continue;
+
+           Y0 = ytr(im, value);
+           if(Y0 <= im->yorigin - im->ysize) break;
+
+           /* draw lines */
+           gfx_new_dashed_line ( im->canvas,
+               X0-1,Y0,
+               X1+1,Y0,
+               GRIDWIDTH, im->graph_col[GRC_GRID],
+               im->grid_dash_on, im->grid_dash_off);
        }
     }
 
-    /* paint major grid and labels*/
-    for (value = pow((double)10, log10(im->minval) 
-                         - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
-        value <= im->maxval;
-        value *= yloglab[majoridx][0]){
-       if (value < im->minval) continue;
-       i=0;    
-       while(yloglab[majoridx][++i] > 0){          
-          Y0 = ytr(im,value * yloglab[majoridx][i]);    
-          if (Y0 <= im->yorigin - im->ysize) break;
-          gfx_new_dashed_line ( im->canvas,
-                         X0-2,Y0,
-                         X1+2,Y0,
-                         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,
-                         X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
-                         im->graph_col[GRC_FONT],
-                         im->text_prop[TEXT_PROP_AXIS].font,
-                         im->text_prop[TEXT_PROP_AXIS].size,
-                         im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
-                         graph_label );
-       } 
-    }
-       return 1;
+    return 1;
 }
 
 
@@ -1769,7 +1995,9 @@ vertical_grid(
        factor=(im->end - im->start)/im->xsize;
        xlab_sel=0;
        while ( xlab[xlab_sel+1].minsec != -1 
-               && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
+               && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; }   /* pick the last one */
+       while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
+               && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; }       /* go back to the smallest size */
        im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
        im->xlab_user.gridst = xlab[xlab_sel].gridst;
        im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
@@ -1845,11 +2073,11 @@ vertical_grid(
 # error "your libc has no strftime I guess we'll abort the exercise here."
 #endif
        gfx_new_text ( im->canvas,
-                     xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size,
+                     xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
                      im->graph_col[GRC_FONT],
                      im->text_prop[TEXT_PROP_AXIS].font,
                      im->text_prop[TEXT_PROP_AXIS].size,
-                     im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
+                     im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
                      graph_label );
        
     }
@@ -1970,6 +2198,17 @@ grid_paint(image_desc_t   *im)
                  5.5, im->tabwidth, 270,
                  GFX_H_RIGHT, GFX_V_TOP,
                  "RRDTOOL / TOBI OETIKER");
+
+    /* graph watermark */
+    if(im->watermark[0] != '\0') {
+        gfx_new_text( im->canvas,
+                  im->ximg/2, im->yimg-6,
+                 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
+                 im->text_prop[TEXT_PROP_AXIS].font,
+                 5.5, im->tabwidth, 0,
+                 GFX_H_CENTER, GFX_V_BOTTOM,
+                 im->watermark);
+    }
     
     /* graph labels */
     if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
@@ -2133,6 +2372,8 @@ graph_size_location(image_desc_t *im, int elements
     ** |v+--+-------------------------------+--------+
     ** | |..............legends......................|
     ** +-+-------------------------------------------+
+    ** |                 watermark                   |
+    ** +---------------------------------------------+
     */
     int Xvertical=0,   
                        Ytitle   =0,
@@ -2145,13 +2386,16 @@ graph_size_location(image_desc_t *im, int elements
 #if 0
        Xlegend  =0,    Ylegend  =0,
 #endif
-        Xspacing =15,  Yspacing =15;
+        Xspacing =15,  Yspacing =15,
+       
+                      Ywatermark =4;
 
     if (im->extra_flags & ONLY_GRAPH) {
        im->xorigin =0;
        im->ximg = im->xsize;
         im->yimg = im->ysize;
         im->yorigin = im->ysize;
+        ytr(im,DNAN); 
        return 0;
     }
 
@@ -2231,18 +2475,19 @@ graph_size_location(image_desc_t *im, int elements
     xtr(im,0);
 
     /* The vertical size is interesting... we need to compare
-    ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
-    ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
-    ** start even thinking about Ylegend.
+    ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with 
+    ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
+    ** in order to start even thinking about Ylegend or Ywatermark.
     **
     ** Do it in three portions: First calculate the inner part,
-    ** then do the legend, then adjust the total height of the img.
+    ** then do the legend, then adjust the total height of the img,
+    ** adding space for a watermark if one exists;
     */
 
     /* reserve space for main and/or pie */
 
     im->yimg = Ymain + Yxlabel;
-
+    
 #ifdef WITH_PIECHART
     if (im->yimg < Ypie) im->yimg = Ypie;
 #endif
@@ -2259,14 +2504,16 @@ graph_size_location(image_desc_t *im, int elements
     }
     /* reserve space for padding below the graph */
     im->yimg += Yspacing;
-    ytr(im,DNAN);
-
+     
     /* Determine where to place the legends onto the image.
     ** Adjust im->yimg to match the space requirements.
     */
     if(leg_place(im)==-1)
        return -1;
-
+       
+    if (im->watermark[0] != '\0') {
+        im->yimg += Ywatermark;
+    }
 
 #if 0
     if (Xlegend > im->ximg) {
@@ -2289,9 +2536,15 @@ graph_size_location(image_desc_t *im, int elements
     }
 #endif
 
+    ytr(im,DNAN);
     return 0;
 }
 
+/* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
+/* yes we are loosing precision by doing tos with floats instead of doubles
+   but it seems more stable this way. */
+   
+
 /* draw that picture thing ... */
 int
 graph_paint(image_desc_t *im, char ***calcpr)
@@ -2306,7 +2559,6 @@ graph_paint(image_desc_t *im, char ***calcpr)
   gfx_node_t *node;
   
   double areazero = 0.0;
-  enum gf_en stack_gf = GF_PRINT;
   graph_desc_t *lastgdes = NULL;    
 
   /* if we are lazy and there is nothing to PRINT ... quit now */
@@ -2430,22 +2682,27 @@ graph_paint(image_desc_t *im, char ***calcpr)
       for (ii = 0; ii < im->xsize; ii++)
         {
           if (!isnan(im->gdes[i].p_data[ii]) && 
-              im->gdes[i].p_data[ii] > 0.0)
-            { 
-              /* generate a tick */
-              gfx_new_line(im->canvas, im -> xorigin + ii, 
-                           im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
-                           im -> xorigin + ii, 
-                           im -> yorigin,
-                           1.0,
-                           im -> gdes[i].col );
-            }
+              im->gdes[i].p_data[ii] != 0.0)
+           { 
+             if (im -> gdes[i].yrule > 0 ) {
+                     gfx_new_line(im->canvas,
+                                   im -> xorigin + ii, im->yorigin,
+                                  im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
+                                  1.0,
+                                  im -> gdes[i].col );
+              } else if ( im -> gdes[i].yrule < 0 ) {
+                     gfx_new_line(im->canvas,
+                                   im -> xorigin + ii, im->yorigin - im -> ysize,
+                                  im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
+                                  1.0,
+                                  im -> gdes[i].col );
+             
+              }
+           }
         }
       break;
     case GF_LINE:
     case GF_AREA:
-      stack_gf = im->gdes[i].gf;
-    case GF_STACK:          
       /* fix data points at oo and -oo */
       for(ii=0;ii<im->xsize;ii++){
         if (isinf(im->gdes[i].p_data[ii])){
@@ -2471,46 +2728,87 @@ graph_paint(image_desc_t *im, char ***calcpr)
       ********************************************************* */
       if (im->gdes[i].col != 0x0){   
         /* GF_LINE and friend */
-        if(stack_gf == GF_LINE ){
+        if(im->gdes[i].gf == GF_LINE ){
+          double last_y=0.0;
           node = NULL;
-          for(ii=1;ii<im->xsize;ii++){     
+          for(ii=1;ii<im->xsize;ii++){
            if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
                node = NULL;
                continue;
            }
             if ( node == NULL ) {
+               last_y = ytr(im,im->gdes[i].p_data[ii]);
                if ( im->slopemode == 0 ){
                   node = gfx_new_line(im->canvas,
-                                    ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
-                                    ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
+                                    ii-1+im->xorigin,last_y,
+                                    ii+im->xorigin,last_y,
                                     im->gdes[i].linewidth,
                                     im->gdes[i].col);
                } else {
                   node = gfx_new_line(im->canvas,
                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
-                                    ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
+                                    ii+im->xorigin,last_y,
                                     im->gdes[i].linewidth,
                                     im->gdes[i].col);
                }
              } else {
-              if ( im->slopemode==0 ){
-                   gfx_add_point(node,ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
+               double new_y = ytr(im,im->gdes[i].p_data[ii]);
+              if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
+                   gfx_add_point(node,ii-1+im->xorigin,new_y);
               };
-               gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
+               last_y = new_y;
+               gfx_add_point(node,ii+im->xorigin,new_y);
              };
 
           }
         } else {
-         double ybase0 = DNAN,ytop0=DNAN;
-          for(ii=0;ii<im->xsize;ii++){
+         int idxI=-1;
+         double *foreY=malloc(sizeof(double)*im->xsize*2);
+         double *foreX=malloc(sizeof(double)*im->xsize*2);
+         double *backY=malloc(sizeof(double)*im->xsize*2);
+         double *backX=malloc(sizeof(double)*im->xsize*2);
+         int drawem = 0;
+          for(ii=0;ii<=im->xsize;ii++){
+           double ybase,ytop;
+           if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
+               int cntI=1;
+               int lastI=0;
+               while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
+               node = gfx_new_area(im->canvas,
+                                backX[0],backY[0],
+                                foreX[0],foreY[0],
+                                foreX[cntI],foreY[cntI], im->gdes[i].col);
+               while (cntI < idxI) {
+                 lastI = cntI;
+                 cntI++;
+                 while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;} 
+                 gfx_add_point(node,foreX[cntI],foreY[cntI]);
+               }
+               gfx_add_point(node,backX[idxI],backY[idxI]);
+               while (idxI > 1){
+                 lastI = idxI;
+                 idxI--;
+                 while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;} 
+                 gfx_add_point(node,backX[idxI],backY[idxI]);
+               }
+               idxI=-1;
+               drawem = 0;
+            }
+            if (drawem != 0){
+              drawem = 0;
+              idxI=-1;
+            }
+            if (ii == im->xsize) break;
+            
            /* keep things simple for now, just draw these bars
               do not try to build a big and complex area */
-           double ybase,ytop;
+
+                                                             
            if ( im->slopemode == 0 && ii==0){
                continue;
            }
            if ( isnan(im->gdes[i].p_data[ii]) ) {
-               ybase0 = DNAN;
+               drawem = 1;
                continue;
            }
             ytop = ytr(im,im->gdes[i].p_data[ii]);
@@ -2520,7 +2818,7 @@ graph_paint(image_desc_t *im, char ***calcpr)
                   ybase = ytr(im,areazero);
             }
             if ( ybase == ytop ){
-               ybase0 = DNAN;
+               drawem = 1;
                continue;       
            }
            /* every area has to be wound clock-wise,
@@ -2530,24 +2828,22 @@ graph_paint(image_desc_t *im, char ***calcpr)
                ytop = ybase;
                ybase = extra;
            }
-           if ( im->slopemode == 0){
-                ybase0 = ybase;
-                ytop0 = ytop;
-           }
-           if ( !isnan(ybase0) ){
-                   node = gfx_new_area(im->canvas,
-                                (double)ii-1.2+(double)im->xorigin,ybase0-0.2,
-                                (double)ii-1.2+(double)im->xorigin,ytop0+0.2,
-                                (double)ii+0.2+(double)im->xorigin,ytop+0.2,
-                                im->gdes[i].col
-                               );
-                   gfx_add_point(node,
-                               (double)ii+0.02+im->xorigin,ybase-0.2
-                              );
+            if ( im->slopemode == 0 ){
+                    backY[++idxI] = ybase-0.2;
+                    backX[idxI] = ii+im->xorigin-1;
+                    foreY[idxI] = ytop+0.2;
+                    foreX[idxI] = ii+im->xorigin-1;
             }
-           ybase0=ybase;
-           ytop0=ytop;
-          }             
+            backY[++idxI] = ybase-0.2;
+            backX[idxI] = ii+im->xorigin;
+            foreY[idxI] = ytop+0.2;
+            foreX[idxI] = ii+im->xorigin;
+          }
+          /* close up any remaining area */             
+          free(foreY);
+          free(foreX);
+          free(backY);
+          free(backX);
         } /* else GF_LINE */
       } /* if color != 0x0 */
       /* make sure we do not run into trouble when stacking on NaN */
@@ -2556,7 +2852,7 @@ graph_paint(image_desc_t *im, char ***calcpr)
           if (lastgdes && (im->gdes[i].stack)) {
             im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
           } else {
-            im->gdes[i].p_data[ii] =  ytr(im,areazero);
+            im->gdes[i].p_data[ii] = areazero;
           }
         }
       } 
@@ -2576,6 +2872,10 @@ graph_paint(image_desc_t *im, char ***calcpr)
       }
       break;
 #endif
+    case GF_STACK:
+      rrd_set_error("STACK should already be turned into LINE or AREA here");
+      return -1;
+      break;
        
     } /* switch */
   }
@@ -2600,9 +2900,6 @@ graph_paint(image_desc_t *im, char ***calcpr)
     
     switch(im->gdes[i].gf){
     case GF_HRULE:
-      if(isnan(im->gdes[i].yrule)) { /* fetch variable */
-        im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
-      };
       if(im->gdes[i].yrule >= im->minval
          && im->gdes[i].yrule <= im->maxval)
         gfx_new_line(im->canvas,
@@ -2611,9 +2908,6 @@ graph_paint(image_desc_t *im, char ***calcpr)
                      1.0,im->gdes[i].col); 
       break;
     case GF_VRULE:
-      if(im->gdes[i].xrule == 0) { /* fetch variable */
-        im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
-      };
       if(im->gdes[i].xrule >= im->start
          && im->gdes[i].xrule <= im->end)
         gfx_new_line(im->canvas,
@@ -2663,10 +2957,14 @@ gdes_alloc(image_desc_t *im){
 
 
     im->gdes[im->gdes_c-1].step=im->step;
+    im->gdes[im->gdes_c-1].step_orig=im->step;
     im->gdes[im->gdes_c-1].stack=0;
+    im->gdes[im->gdes_c-1].linewidth=0;
     im->gdes[im->gdes_c-1].debug=0;
     im->gdes[im->gdes_c-1].start=im->start; 
+    im->gdes[im->gdes_c-1].start_orig=im->start; 
     im->gdes[im->gdes_c-1].end=im->end; 
+    im->gdes[im->gdes_c-1].end_orig=im->end; 
     im->gdes[im->gdes_c-1].vname[0]='\0'; 
     im->gdes[im->gdes_c-1].data=NULL;
     im->gdes[im->gdes_c-1].ds_namv=NULL;
@@ -2677,8 +2975,11 @@ gdes_alloc(image_desc_t *im){
     im->gdes[im->gdes_c-1].col = 0x0;
     im->gdes[im->gdes_c-1].legend[0]='\0';
     im->gdes[im->gdes_c-1].format[0]='\0';
+    im->gdes[im->gdes_c-1].strftm=0;   
     im->gdes[im->gdes_c-1].rrd[0]='\0';
     im->gdes[im->gdes_c-1].ds=-1;    
+    im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;    
+    im->gdes[im->gdes_c-1].cf=CF_AVERAGE;    
     im->gdes[im->gdes_c-1].p_data=NULL;    
     im->gdes[im->gdes_c-1].yrule=DNAN;
     im->gdes[im->gdes_c-1].xrule=0;
@@ -2688,7 +2989,7 @@ gdes_alloc(image_desc_t *im){
 /* copies input untill the first unescaped colon is found
    or until input ends. backslashes have to be escaped as well */
 int
-scan_for_col(char *input, int len, char *output)
+scan_for_col(const char *const input, int len, char *const output)
 {
     int inp,outp=0;
     for (inp=0; 
@@ -2794,6 +3095,9 @@ rrd_graph_init(image_desc_t *im)
 #endif
 #ifdef HAVE_SETLOCALE
     setlocale(LC_TIME,"");
+#ifdef HAVE_MBSTOWCS
+    setlocale(LC_CTYPE,"");
+#endif
 #endif
     im->yorigin=0;
     im->xorigin=0;
@@ -2806,6 +3110,7 @@ rrd_graph_init(image_desc_t *im)
     im->step = 0;
     im->ylegend[0] = '\0';
     im->title[0] = '\0';
+    im->watermark[0] = '\0';
     im->minval = DNAN;
     im->maxval = DNAN;    
     im->unitsexponent= 9999;
@@ -2841,8 +3146,8 @@ rrd_graph_init(image_desc_t *im)
             windir = getenv("windir");
             /* %windir% is something like D:\windows or C:\winnt */
             if (windir != NULL) {
-                    strncpy(rrd_win_default_font,windir,999);
-                    rrd_win_default_font[999] = '\0';
+                    strncpy(rrd_win_default_font,windir,500);
+                    rrd_win_default_font[500] = '\0';
                     strcat(rrd_win_default_font,"\\fonts\\");
                    strcat(rrd_win_default_font,RRD_DEFAULT_FONT);         
                     for(i=0;i<DIM(text_prop);i++){
@@ -2883,6 +3188,10 @@ rrd_graph_options(int argc, char *argv[],image_desc_t *im)
     parsetime("end-24h", &start_tv);
     parsetime("now", &end_tv);
 
+    /* defines for long options without a short equivalent. should be bytes,
+       and may not collide with (the ASCII value of) short options */
+    #define LONGOPT_UNITS_SI 255
+
     while (1){
        static struct option long_options[] =
        {
@@ -2917,10 +3226,12 @@ rrd_graph_options(int argc, char *argv[],image_desc_t *im)
             {"no-gridfit", no_argument,       0,   'N'},
            {"units-exponent",required_argument, 0, 'X'},
            {"units-length",required_argument, 0, 'L'},
+            {"units",      required_argument, 0,  LONGOPT_UNITS_SI },
            {"step",       required_argument, 0,    'S'},
             {"tabwidth",   required_argument, 0,    'T'},            
            {"font-render-mode", required_argument, 0, 'R'},
            {"font-smoothing-threshold", required_argument, 0, 'B'},
+           {"watermark",  required_argument, 0,  'W'},
            {"alt-y-mrtg", no_argument,       0,  1000}, /* this has no effect it is just here to save old apps from crashing when they use it */
            {0,0,0,0}};
        int option_index = 0;
@@ -2928,7 +3239,7 @@ rrd_graph_options(int argc, char *argv[],image_desc_t *im)
         int col_start,col_end;
 
        opt = getopt_long(argc, argv, 
-                        "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:",
+                        "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
                          long_options, &option_index);
 
        if (opt == EOF)
@@ -2956,6 +3267,18 @@ rrd_graph_options(int argc, char *argv[],image_desc_t *im)
        case 'F':
            im->extra_flags |= FORCE_RULES_LEGEND;
            break;
+       case LONGOPT_UNITS_SI:
+           if(im->extra_flags & FORCE_UNITS) {
+               rrd_set_error("--units can only be used once!");
+               return;
+           }
+           if(strcmp(optarg,"si")==0)
+               im->extra_flags |= FORCE_UNITS_SI;
+           else {
+               rrd_set_error("invalid argument for --units: %s", optarg );
+               return;
+           }
+           break;
        case 'X':
            im->unitsexponent = atoi(optarg);
            break;
@@ -3098,8 +3421,6 @@ rrd_graph_options(int argc, char *argv[],image_desc_t *im)
 
        case 'o':
            im->logarithmic = 1;
-           if (isnan(im->minval))
-               im->minval=1;
            break;
         case 'c':
             if(sscanf(optarg,
@@ -3147,25 +3468,22 @@ rrd_graph_options(int argc, char *argv[],image_desc_t *im)
         case 'n':{
            char prop[15];
            double size = 1;
-           char font[1024];
+           char font[1024] = "";
 
            if(sscanf(optarg,
                                "%10[A-Z]:%lf:%1000s",
-                               prop,&size,font) == 3){
-               int sindex;
+                               prop,&size,font) >= 2){
+               int sindex,propidx;
                if((sindex=text_prop_conv(prop)) != -1){
-                   im->text_prop[sindex].size=size;              
-                   strcpy(im->text_prop[sindex].font,font);
-                   if (sindex==0) { /* the default */
-                       im->text_prop[TEXT_PROP_TITLE].size=size;
-                       strcpy(im->text_prop[TEXT_PROP_TITLE].font,font);
-                       im->text_prop[TEXT_PROP_AXIS].size=size;
-                       strcpy(im->text_prop[TEXT_PROP_AXIS].font,font);
-                       im->text_prop[TEXT_PROP_UNIT].size=size;
-                       strcpy(im->text_prop[TEXT_PROP_UNIT].font,font);
-                       im->text_prop[TEXT_PROP_LEGEND].size=size;
-                       strcpy(im->text_prop[TEXT_PROP_LEGEND].font,font);
-                   }
+                  for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){                     
+                     if (size > 0){
+                         im->text_prop[propidx].size=size;              
+                      }
+                     if (strlen(font) > 0){
+                         strcpy(im->text_prop[propidx].font,font);
+                      }
+                      if (propidx==sindex && sindex != 0) break;
+                  }
                } else {
                    rrd_set_error("invalid fonttag '%s'",prop);
                    return;
@@ -3206,6 +3524,11 @@ rrd_graph_options(int argc, char *argv[],image_desc_t *im)
            im->canvas->font_aa_threshold = atof(optarg);
                break;
 
+        case 'W':
+            strncpy(im->watermark,optarg,100);
+            im->watermark[99]='\0';
+            break;
+
        case '?':
             if (optopt != 0)
                 rrd_set_error("unknown option '%c'", optopt);
@@ -3220,7 +3543,7 @@ rrd_graph_options(int argc, char *argv[],image_desc_t *im)
        return;
     }
 
-    if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
+    if (im->logarithmic == 1 && im->minval <= 0){
        rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");    
        return;
     }
@@ -3311,12 +3634,17 @@ int bad_format(char *fmt) {
              /* '%s', '%S' and '%%' are allowed */
              if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
 
+             /* %c is allowed (but use only with vdef!) */
+            else if (*ptr == 'c') {
+               ptr++;
+               n=1;
+            }
+
              /* 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++;
@@ -3337,7 +3665,7 @@ int bad_format(char *fmt) {
 int
 vdef_parse(gdes,str)
 struct graph_desc_t *gdes;
-char *str;
+const char *const str;
 {
     /* A VDEF currently is either "func" or "param,func"
      * so the parsing is rather simple.  Change if needed.
@@ -3370,6 +3698,9 @@ char *str;
     else if    (!strcmp("TOTAL",  func)) gdes->vf.op = VDEF_TOTAL;
     else if    (!strcmp("FIRST",  func)) gdes->vf.op = VDEF_FIRST;
     else if    (!strcmp("LAST",   func)) gdes->vf.op = VDEF_LAST;
+    else if     (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
+    else if     (!strcmp("LSLINT",   func)) gdes->vf.op = VDEF_LSLINT;
+    else if     (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
     else {
        rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
            ,func
@@ -3405,6 +3736,9 @@ char *str;
        case VDEF_TOTAL:
        case VDEF_FIRST:
        case VDEF_LAST:
+       case VDEF_LSLSLOPE:
+       case VDEF_LSLINT:
+       case VDEF_LSLCORREL:
            if (isnan(param)) {
                gdes->vf.param = DNAN;
                gdes->vf.val   = DNAN;
@@ -3502,7 +3836,7 @@ printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
            if (cnt) {
                if (dst->vf.op == VDEF_TOTAL) {
                    dst->vf.val  = sum*src->step;
-                   dst->vf.when = cnt*src->step;       /* not really "when" */
+                   dst->vf.when = 0;   /* no time component */
                } else {
                    dst->vf.val = sum/cnt;
                    dst->vf.when = 0;   /* no time component */
@@ -3563,6 +3897,48 @@ printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
                dst->vf.when = src->start + (step+1)*src->step;
            }
            break;
+       case VDEF_LSLSLOPE:
+       case VDEF_LSLINT:
+       case VDEF_LSLCORREL:{
+           /* Bestfit line by linear least squares method */ 
+
+           int cnt=0;
+           double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
+           SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
+
+           for (step=0;step<steps;step++) {
+               if (finite(data[step*src->ds_cnt])) {
+                   cnt++;
+                   SUMx  += step;
+                   SUMxx += step * step;
+                   SUMxy += step * data[step*src->ds_cnt];
+                   SUMy  += data[step*src->ds_cnt];
+                   SUMyy  += data[step*src->ds_cnt]*data[step*src->ds_cnt];
+               };
+           }
+
+           slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
+           y_intercept = ( SUMy - slope*SUMx ) / cnt;
+           correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
+
+           if (cnt) {
+                   if (dst->vf.op == VDEF_LSLSLOPE) {
+                       dst->vf.val  = slope;
+                       dst->vf.when = 0;
+                   } else if (dst->vf.op == VDEF_LSLINT)  {
+                       dst->vf.val = y_intercept;
+                       dst->vf.when = 0;
+                   } else if (dst->vf.op == VDEF_LSLCORREL)  {
+                       dst->vf.val = correl;
+                       dst->vf.when = 0;
+                   };
+               
+           } else {
+               dst->vf.val  = DNAN;
+               dst->vf.when = 0;
+           }
+       }
+       break;
     }
     return 0;
 }