watermartk feature for rrdgraph by Ronan Mullally
[rrdtool.git] / src / rrd_graph.c
index a06417e..a71a174 100644 (file)
@@ -1,5 +1,5 @@
 /****************************************************************************
- * RRDtool 1.2.10  Copyright by Tobi Oetiker, 1997-2005
+ * RRDtool 1.2.12  Copyright by Tobi Oetiker, 1997-2005
  ****************************************************************************
  * rrd__graph.c  produce graphs from data in rrdfiles
  ****************************************************************************/
@@ -42,22 +42,25 @@ 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"},
+    {180,               0,   TMT_HOUR,1,    TMT_HOUR,6,    TMT_HOUR,6,           0,"%H:%M"},
+    {180,       1*24*3600,   TMT_HOUR,1,    TMT_HOUR,6,    TMT_HOUR,6,           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"},
+    {600,       1*24*3600,   TMT_HOUR,6,    TMT_DAY,1,     TMT_DAY,1,      24*3600,"%a %d"},
+    {1800,              0,   TMT_HOUR,12,   TMT_DAY,1,     TMT_DAY,2,      24*3600,"%a"},
+    {1800,      1*24*3600,   TMT_HOUR,12,   TMT_DAY,1,     TMT_DAY,2,      24*3600,"%a %d"},
+    {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"},
+    {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 ...
@@ -176,7 +179,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)
@@ -688,7 +691,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) 
@@ -702,9 +705,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
                */
@@ -1017,8 +1020,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");
@@ -1040,7 +1042,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
@@ -1079,6 +1080,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;
            }
@@ -1358,7 +1363,6 @@ print_calc(image_desc_t *im, char ***prdata)
        case GF_LINE:
        case GF_AREA:
        case GF_TICK:
-       case GF_STACK:
        case GF_HRULE:
        case GF_VRULE:
            graphelement = 1;
@@ -1373,6 +1377,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;
@@ -1524,11 +1532,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;
 
@@ -1579,17 +1586,16 @@ calc_horizontal_grid(image_desc_t   *im)
        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;
@@ -1768,7 +1774,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;
@@ -1969,6 +1977,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) ) {
@@ -2132,6 +2151,8 @@ graph_size_location(image_desc_t *im, int elements
     ** |v+--+-------------------------------+--------+
     ** | |..............legends......................|
     ** +-+-------------------------------------------+
+    ** |                 watermark                   |
+    ** +---------------------------------------------+
     */
     int Xvertical=0,   
                        Ytitle   =0,
@@ -2144,7 +2165,9 @@ 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;
@@ -2231,24 +2254,24 @@ 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
 
     im->yorigin = im->yimg - Yxlabel;
-    ytr(im,DNAN); 
 
     /* reserve space for the title *or* some padding above the graph */
     if (Ytitle) {
@@ -2260,13 +2283,16 @@ graph_size_location(image_desc_t *im, int elements
     }
     /* reserve space for padding below the graph */
     im->yimg += Yspacing;
-
+     
     /* 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,6 +2315,40 @@ 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. */
+   
+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;
 }
 
@@ -2306,7 +2366,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 +2489,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,32 +2535,36 @@ 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);
              };
 
           }
@@ -2512,7 +2580,7 @@ graph_paint(image_desc_t *im, char ***calcpr)
            if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
                int cntI=1;
                int lastI=0;
-               while (cntI < idxI && foreY[lastI] == foreY[cntI] && foreY[lastI] == foreY[cntI+1]){cntI++;}
+               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],
@@ -2520,14 +2588,14 @@ graph_paint(image_desc_t *im, char ***calcpr)
                while (cntI < idxI) {
                  lastI = cntI;
                  cntI++;
-                 while ( cntI < idxI && foreY[lastI] == foreY[cntI] && foreY[lastI] == foreY[cntI+1]){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 && backY[lastI] == backY[idxI] && backY[lastI] == backY[idxI-1]){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;
@@ -2611,6 +2679,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 */
   }
@@ -2698,7 +2770,9 @@ 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].end=im->end; 
@@ -2723,7 +2797,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; 
@@ -2829,6 +2903,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;
@@ -2841,6 +2918,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;
@@ -2876,8 +2954,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++){
@@ -2956,6 +3034,7 @@ rrd_graph_options(int argc, char *argv[],image_desc_t *im)
             {"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;
@@ -2963,7 +3042,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)
@@ -3182,7 +3261,7 @@ 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",
@@ -3238,6 +3317,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);
@@ -3343,12 +3427,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++;
@@ -3369,7 +3458,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.
@@ -3402,6 +3491,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
@@ -3437,6 +3529,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;
@@ -3595,6 +3690,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 = cnt*src->step;
+                   } else if (dst->vf.op == VDEF_LSLINT)  {
+                       dst->vf.val = y_intercept;
+                       dst->vf.when = cnt*src->step;
+                   } else if (dst->vf.op == VDEF_LSLCORREL)  {
+                       dst->vf.val = correl;
+                       dst->vf.when = cnt*src->step;
+                   };
+               
+           } else {
+               dst->vf.val  = DNAN;
+               dst->vf.when = 0;
+           }
+           }
+           break;
     }
     return 0;
 }