prepare for the release of rrdtool-1.2.13
[rrdtool.git] / src / rrd_graph.c
index a4f250c..67d7f05 100644 (file)
@@ -1,5 +1,5 @@
 /****************************************************************************
- * RRDtool 1.2.11  Copyright by Tobi Oetiker, 1997-2005
+ * RRDtool 1.2.13  Copyright by Tobi Oetiker, 1997-2006
  ****************************************************************************
  * rrd__graph.c  produce graphs from data in rrdfiles
  ****************************************************************************/
@@ -179,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)
@@ -312,14 +312,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 */
@@ -331,9 +325,17 @@ si_unit(
                     'G', /* 10e9   Giga */
                     'T', /* 10e12  Tera */
                     'P', /* 10e15  Peta */
-                    'E'};/* 10e18  Exa */
+                    'E', /* 10e18  Exa */
+};
+const static 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)); 
@@ -353,9 +355,9 @@ si_unit(
 
     im->viewfactor = im->magfact / 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 = '?';
  }
@@ -1020,8 +1022,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");
@@ -1043,7 +1044,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
@@ -1082,6 +1082,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;
            }
@@ -1361,7 +1365,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;
@@ -1376,6 +1379,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;
@@ -1527,11 +1534,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;
 
@@ -1582,17 +1588,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;
@@ -1609,6 +1614,7 @@ 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;
    
@@ -1619,9 +1625,13 @@ int draw_horizontal_grid(image_desc_t *im)
     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){               
+           /* 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);
@@ -1644,6 +1654,7 @@ int draw_horizontal_grid(image_desc_t *im)
                        }
                     }
                }
+               nlabels++;
 
               gfx_new_text ( im->canvas,
                              X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
@@ -1737,8 +1748,24 @@ horizontal_log_grid(image_desc_t   *im)
                          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]);
+
+          if (im->extra_flags & FORCE_UNITS_SI) {
+             double pvalue = value * yloglab[majoridx][i];
+             double scale = floor( log10( fabs(pvalue)) / 3);
+             char symbol;
+
+             pvalue /= pow(10, 3*scale);
+
+             if ( ((scale+si_symbcenter) < sizeof(si_symbol)) &&
+                  ((scale+si_symbcenter) >= 0) )
+                symbol = si_symbol[(int)scale+si_symbcenter];
+             else
+                symbol = '?';
+
+             sprintf(graph_label,"%3.0f %c", pvalue, symbol);
+          } else
+             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],
@@ -1974,6 +2001,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) ) {
@@ -2137,6 +2175,8 @@ graph_size_location(image_desc_t *im, int elements
     ** |v+--+-------------------------------+--------+
     ** | |..............legends......................|
     ** +-+-------------------------------------------+
+    ** |                 watermark                   |
+    ** +---------------------------------------------+
     */
     int Xvertical=0,   
                        Ytitle   =0,
@@ -2149,7 +2189,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;
@@ -2236,12 +2278,13 @@ 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 */
@@ -2270,7 +2313,10 @@ graph_size_location(image_desc_t *im, int elements
     */
     if(leg_place(im)==-1)
        return -1;
-
+       
+    if (im->watermark[0] != '\0') {
+        im->yimg += Ywatermark;
+    }
 
 #if 0
     if (Xlegend > im->ximg) {
@@ -2344,7 +2390,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 */
@@ -2468,22 +2513,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])){
@@ -2509,7 +2559,7 @@ 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++){
@@ -2653,6 +2703,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 */
   }
@@ -2742,9 +2796,12 @@ 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;
@@ -2757,6 +2814,8 @@ gdes_alloc(image_desc_t *im){
     im->gdes[im->gdes_c-1].format[0]='\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;
@@ -2766,7 +2825,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; 
@@ -2872,6 +2931,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;
@@ -2884,6 +2946,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;
@@ -2919,8 +2982,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++){
@@ -2961,6 +3024,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[] =
        {
@@ -2995,10 +3062,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;
@@ -3006,7 +3075,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)
@@ -3034,6 +3103,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;
@@ -3281,6 +3362,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);
@@ -3386,12 +3472,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++;
@@ -3412,7 +3503,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.