Evenly-spaced y-axis gridlines.
authoroetiker <oetiker@a5681a0c-68f1-0310-ab6d-d61299d08faa>
Mon, 15 Apr 2002 21:29:24 +0000 (21:29 +0000)
committeroetiker <oetiker@a5681a0c-68f1-0310-ab6d-d61299d08faa>
Mon, 15 Apr 2002 21:29:24 +0000 (21:29 +0000)
I had to split horizontal_grid() into calc_horizontal_grid and
draw_horizontal_grid as the calculated info is needed in the adjustment
code. My previous patch had the problem that it adjusted the y-axis too
late, e.g. after the data lines was drawn.
The result of the calc is stored in struct ygrid_scale_t which
image_desc_t has as a member.

--no-gridfit is implemented.

The round-to-integer coordinates for png is moved to the libart code in
rrd_gfx. The 'close path' code is cleaned up so the node list is left
unchanged if you want to save the same graph in multiple formats in one
run. The rounding is done on the scaled coordinates (zoom).

I have made a simple version for logarithmic y scales as such a scale
might have 5 gridlines with 4 difference spacings. This version only
uses y = 10^x values for modifying the scale. -- Peter Speck <speck@ruc.dk>

git-svn-id: svn://svn.oetiker.ch/rrdtool/trunk/program@123 a5681a0c-68f1-0310-ab6d-d61299d08faa

src/rrd_gfx.c
src/rrd_graph.c
src/rrd_graph.h

index 5fd0060..4ddd584 100644 (file)
@@ -169,6 +169,8 @@ int           gfx_add_point  (gfx_node_t *node,
 
 void           gfx_close_path  (gfx_node_t *node) {
     node->closed_path = 1;
 
 void           gfx_close_path  (gfx_node_t *node) {
     node->closed_path = 1;
+    if (node->path[0].code == ART_MOVETO_OPEN)
+       node->path[0].code = ART_MOVETO;
 }
 
 /* create a text node */
 }
 
 /* create a text node */
@@ -280,6 +282,29 @@ double gfx_get_text_width_libart ( gfx_canvas_t *canvas,
   return text_width;
 }
  
   return text_width;
 }
  
+static void gfx_libart_close_path(gfx_canvas_t *canvas,
+       gfx_node_t *node, ArtVpath **vec)
+{
+    /* libart must have end==start for closed paths,
+       even if using ART_MOVETO and not ART_MOVETO_OPEN
+       so add extra point which is the same as the starting point */
+    int points_max = node->points; /* scaled array has exact size */
+    int points = node->points - 1;
+    art_vpath_add_point (vec, &points, &points_max, ART_LINETO,
+           (**vec).x, (**vec).y);
+    art_vpath_add_point (vec, &points, &points_max, ART_END, 0, 0);
+}
+
+static void gfx_round_scaled_coordinates(gfx_canvas_t *canvas,
+       gfx_node_t *node, ArtVpath *vec)
+{
+    while (vec->code != ART_END) {
+       vec->x = floor(vec->x - LINEOFFSET + 0.5) + LINEOFFSET;
+       vec->y = floor(vec->y - LINEOFFSET + 0.5) + LINEOFFSET;
+       vec++;
+    }
+}
+
 static int gfx_save_png (art_u8 *buffer, FILE *fp,
                      long width, long height, long bytes_per_pixel);
 /* render grafics into png image */
 static int gfx_save_png (art_u8 *buffer, FILE *fp,
                      long width, long height, long bytes_per_pixel);
 /* render grafics into png image */
@@ -306,13 +331,11 @@ int           gfx_render_png (gfx_canvas_t *canvas,
             ArtVpath *vec;
             double dst[6];     
             ArtSVP *svp;
             ArtVpath *vec;
             double dst[6];     
             ArtSVP *svp;
-           if (node->closed_path) { 
-               /* libart uses end==start for closed as indicator of closed path */
-               gfx_add_point(node, node->path[0].x, node->path[0].y);
-               node->closed_path = 0;
-           }
             art_affine_scale(dst,canvas->zoom,canvas->zoom);
             vec = art_vpath_affine_transform(node->path,dst);
             art_affine_scale(dst,canvas->zoom,canvas->zoom);
             vec = art_vpath_affine_transform(node->path,dst);
+           if (node->closed_path)
+               gfx_libart_close_path(canvas, node, &vec);
+           gfx_round_scaled_coordinates(canvas, node, vec);
             if(node->type == GFX_LINE){
                 svp = art_svp_vpath_stroke ( vec, ART_PATH_STROKE_JOIN_ROUND,
                                              ART_PATH_STROKE_CAP_ROUND,
             if(node->type == GFX_LINE){
                 svp = art_svp_vpath_stroke ( vec, ART_PATH_STROKE_JOIN_ROUND,
                                              ART_PATH_STROKE_CAP_ROUND,
index c58326f..2e6cb9f 100644 (file)
@@ -129,7 +129,7 @@ xtr(image_desc_t *im,time_t mytime){
 }
 
 /* translate data values into y coordinates */
 }
 
 /* translate data values into y coordinates */
-int
+double
 ytr(image_desc_t *im, double value){
     static double pixie;
     double yval;
 ytr(image_desc_t *im, double value){
     static double pixie;
     double yval;
@@ -140,26 +140,25 @@ ytr(image_desc_t *im, double value){
        pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
       yval = im->yorigin;
     } else if(!im->logarithmic) {
        pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
       yval = im->yorigin;
     } else if(!im->logarithmic) {
-      yval = im->yorigin - pixie * (value - im->minval) + 0.5;
+      yval = im->yorigin - pixie * (value - im->minval);
     } else {
       if (value < im->minval) {
        yval = im->yorigin;
       } else {
     } else {
       if (value < im->minval) {
        yval = im->yorigin;
       } else {
-       yval = im->yorigin - pixie * (log10(value) - log10(im->minval)) + 0.5;
+       yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
       }
     }
     /* make sure we don't return anything too unreasonable. GD lib can
        get terribly slow when drawing lines outside its scope. This is 
        especially problematic in connection with the rigid option */
     if (! im->rigid) {
       }
     }
     /* make sure we don't return anything too unreasonable. GD lib can
        get terribly slow when drawing lines outside its scope. This is 
        especially problematic in connection with the rigid option */
     if (! im->rigid) {
-      return (int)yval;
-    } else if ((int)yval > im->yorigin) {
-      return im->yorigin+2;
-    } else if ((int) yval < im->yorigin - im->ysize){
-      return im->yorigin - im->ysize - 2;
-    } else {
-      return (int)yval;
+      /* keep yval as-is */
+    } else if (yval > im->yorigin) {
+      yval = im->yorigin+2;
+    } else if (yval < im->yorigin - im->ysize){
+      yval = im->yorigin - im->ysize - 2;
     } 
     } 
+    return yval;
 }
 
 
 }
 
 
@@ -438,7 +437,73 @@ expand_range(image_desc_t *im)
 #endif
 }
 
 #endif
 }
 
-    
+void
+apply_gridfit(image_desc_t *im)
+{
+  if (isnan(im->minval) || isnan(im->maxval))
+    return;
+  ytr(im,DNAN);
+  if (im->logarithmic) {
+    double ya, yb, ypix, ypixfrac;
+    double log10_range = log10(im->maxval) - log10(im->minval);
+    ya = pow((double)10, floor(log10(im->minval)));
+    while (ya < im->minval)
+      ya *= 10;
+    if (ya > im->maxval)
+      return; /* don't have y=10^x gridline */
+    yb = ya * 10;
+    if (yb <= im->maxval) {
+      /* we have at least 2 y=10^x gridlines.
+        Make sure distance between them in pixels
+        are an integer by expanding im->maxval */
+      double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
+      double factor = y_pixel_delta / floor(y_pixel_delta);
+      double new_log10_range = factor * log10_range;
+      double new_ymax_log10 = log10(im->minval) + new_log10_range;
+      im->maxval = pow(10, new_ymax_log10);
+      ytr(im, DNAN); /* reset precalc */
+      log10_range = log10(im->maxval) - log10(im->minval);
+    }
+    /* make sure first y=10^x gridline is located on 
+       integer pixel position by moving scale slightly 
+       downwards (sub-pixel movement) */
+    ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
+    ypixfrac = ypix - floor(ypix);
+    if (ypixfrac > 0 && ypixfrac < 1) {
+      double yfrac = ypixfrac / im->ysize;
+      im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
+      im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
+      ytr(im, DNAN); /* reset precalc */
+    }
+  } else {
+    /* Make sure we have an integer pixel distance between
+       each minor gridline */
+    double ypos1 = ytr(im, im->minval);
+    double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
+    double y_pixel_delta = ypos1 - ypos2;
+    double factor = y_pixel_delta / floor(y_pixel_delta);
+    double new_range = factor * (im->maxval - im->minval);
+    double gridstep = im->ygrid_scale.gridstep;
+    double minor_y, minor_y_px, minor_y_px_frac;
+    im->maxval = im->minval + new_range;
+    ytr(im, DNAN); /* reset precalc */
+    /* make sure first minor gridline is on integer pixel y coord */
+    minor_y = gridstep * floor(im->minval / gridstep);
+    while (minor_y < im->minval)
+      minor_y += gridstep;
+    minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
+    minor_y_px_frac = minor_y_px - floor(minor_y_px);
+    if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
+      double yfrac = minor_y_px_frac / im->ysize;
+      double range = im->maxval - im->minval;
+      im->minval = im->minval - yfrac * range;
+      im->maxval = im->maxval - yfrac * range;
+      ytr(im, DNAN); /* reset precalc */
+    }
+    calc_horizontal_grid(im); /* recalc with changed im->maxval */
+  }
+}
+
 /* reduce data reimplementation by Alex */
 
 void
 /* reduce data reimplementation by Alex */
 
 void
@@ -1385,21 +1450,15 @@ leg_place(image_desc_t *im)
 
 
 int
 
 
 int
-horizontal_grid(image_desc_t   *im)
+calc_horizontal_grid(image_desc_t   *im)
 {
     double   range;
     double   scaledrange;
     int      pixel,i;
 {
     double   range;
     double   scaledrange;
     int      pixel,i;
-    int      sgrid,egrid;
-    double   gridstep;
-    double   scaledstep;
-    char     graph_label[100];
-    double   X0,X1,Y0;
-    int      labfact,gridind;
+    int      gridind;
     int      decimals, fractionals;
     int      decimals, fractionals;
-    char     labfmt[64];
 
 
-    labfact=2;
+    im->ygrid_scale.labfact=2;
     gridind=-1;
     range =  im->maxval - im->minval;
     scaledrange = range / im->magfact;
     gridind=-1;
     range =  im->maxval - im->minval;
     scaledrange = range / im->magfact;
@@ -1421,25 +1480,25 @@ horizontal_grid(image_desc_t   *im)
            
            fractionals = floor(log10(range));
            if(fractionals < 0) /* small amplitude. */
            
            fractionals = floor(log10(range));
            if(fractionals < 0) /* small amplitude. */
-               sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
+               sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
            else
            else
-               sprintf(labfmt, "%%%d.1f", decimals + 1);
-           gridstep = pow((double)10, (double)fractionals);
-           if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
-               gridstep = 0.1;
+               sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
+           im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
+           if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
+               im->ygrid_scale.gridstep = 0.1;
            /* should have at least 5 lines but no more then 15 */
            /* should have at least 5 lines but no more then 15 */
-           if(range/gridstep < 5)
-                gridstep /= 10;
-           if(range/gridstep > 15)
-                gridstep *= 10;
-           if(range/gridstep > 5) {
-               labfact = 1;
-               if(range/gridstep > 8)
-                   labfact = 2;
+           if(range/im->ygrid_scale.gridstep < 5)
+                im->ygrid_scale.gridstep /= 10;
+           if(range/im->ygrid_scale.gridstep > 15)
+                im->ygrid_scale.gridstep *= 10;
+           if(range/im->ygrid_scale.gridstep > 5) {
+               im->ygrid_scale.labfact = 1;
+               if(range/im->ygrid_scale.gridstep > 8)
+                   im->ygrid_scale.labfact = 2;
            }
            else {
            }
            else {
-               gridstep /= 5;
-               labfact = 5;
+               im->ygrid_scale.gridstep /= 5;
+               im->ygrid_scale.labfact = 5;
            }
        }
        else {
            }
        }
        else {
@@ -1453,33 +1512,40 @@ horizontal_grid(image_desc_t   *im)
            
            for(i=0; i<4;i++) {
               if (pixel * ylab[gridind].lfac[i] >=  2 * im->text_prop[TEXT_PROP_AXIS].size) {
            
            for(i=0; i<4;i++) {
               if (pixel * ylab[gridind].lfac[i] >=  2 * im->text_prop[TEXT_PROP_AXIS].size) {
-                 labfact =  ylab[gridind].lfac[i];
+                 im->ygrid_scale.labfact =  ylab[gridind].lfac[i];
                  break;
               }                          
            } 
            
                  break;
               }                          
            } 
            
-           gridstep = ylab[gridind].grid * im->magfact;
+           im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
        }
     } else {
        }
     } else {
-       gridstep = im->ygridstep;
-       labfact = im->ylabfact;
+       im->ygrid_scale.gridstep = im->ygridstep;
+       im->ygrid_scale.labfact = im->ylabfact;
     }
     }
-    
-   X0=im->xorigin;
-   X1=im->xorigin+im->xsize;
+    return 1;
+}
+
+int draw_horizontal_grid(image_desc_t *im)
+{
+    int      i;
+    double   scaledstep;
+    char     graph_label[100];
+    double X0=im->xorigin;
+    double X1=im->xorigin+im->xsize;
    
    
-    sgrid = (int)( im->minval / gridstep - 1);
-    egrid = (int)( im->maxval / gridstep + 1);
-    scaledstep = gridstep/im->magfact;
+    int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
+    int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
+    scaledstep = im->ygrid_scale.gridstep/im->magfact;
     for (i = sgrid; i <= egrid; i++){
     for (i = sgrid; i <= egrid; i++){
-       Y0=ytr(im,gridstep*i);
+       double Y0=ytr(im,im->ygrid_scale.gridstep*i);
        if ( Y0 >= im->yorigin-im->ysize
                 && Y0 <= im->yorigin){       
        if ( Y0 >= im->yorigin-im->ysize
                 && Y0 <= im->yorigin){       
-           if(i % labfact == 0){               
+           if(i % im->ygrid_scale.labfact == 0){               
                if (i==0 || im->symbol == ' ') {
                    if(scaledstep < 1){
                        if(im->extra_flags & ALTYGRID) {
                if (i==0 || im->symbol == ' ') {
                    if(scaledstep < 1){
                        if(im->extra_flags & ALTYGRID) {
-                           sprintf(graph_label,labfmt,scaledstep*i);
+                           sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
                        }
                        else {
                            sprintf(graph_label,"%4.1f",scaledstep*i);
                        }
                        else {
                            sprintf(graph_label,"%4.1f",scaledstep*i);
@@ -1757,7 +1823,7 @@ grid_paint(image_desc_t   *im)
        if(im->logarithmic){
                res = horizontal_log_grid(im);
        } else {
        if(im->logarithmic){
                res = horizontal_log_grid(im);
        } else {
-               res = horizontal_grid(im);
+               res = draw_horizontal_grid(im);
        }
 
        /* dont draw horizontal grid if there is no min and max val */
        }
 
        /* dont draw horizontal grid if there is no min and max val */
@@ -2135,6 +2201,11 @@ graph_paint(image_desc_t *im, char ***calcpr)
     expand_range(im);   /* make sure the upper and lower limit are
                            sensible values */
 
     expand_range(im);   /* make sure the upper and lower limit are
                            sensible values */
 
+  if (!calc_horizontal_grid(im))
+    return -1;
+  if (im->gridfit)
+    apply_gridfit(im);
+
 /**************************************************************
  *** Calculating sizes and locations became a bit confusing ***
  *** so I moved this into a separate function.              ***
 /**************************************************************
  *** Calculating sizes and locations became a bit confusing ***
  *** so I moved this into a separate function.              ***
@@ -2521,6 +2592,7 @@ rrd_graph_init(image_desc_t *im)
     im->unitsexponent= 9999;
     im->extra_flags= 0;
     im->rigid = 0;
     im->unitsexponent= 9999;
     im->extra_flags= 0;
     im->rigid = 0;
+    im->gridfit = 1;
     im->imginfo = NULL;
     im->lazy = 0;
     im->logarithmic = 0;
     im->imginfo = NULL;
     im->lazy = 0;
     im->logarithmic = 0;
@@ -2585,6 +2657,7 @@ rrd_graph_options(int argc, char *argv[],image_desc_t *im)
            {"alt-autoscale-max", no_argument,    0,   259 },
            {"units-exponent",required_argument, 0,  260},
            {"step",       required_argument, 0,   261},
            {"alt-autoscale-max", no_argument,    0,   259 },
            {"units-exponent",required_argument, 0,  260},
            {"step",       required_argument, 0,   261},
+           {"no-gridfit", no_argument,       0,   262},
            {0,0,0,0}};
        int option_index = 0;
        int opt;
            {0,0,0,0}};
        int option_index = 0;
        int opt;
@@ -2616,6 +2689,9 @@ rrd_graph_options(int argc, char *argv[],image_desc_t *im)
        case 261:
            im->step =  atoi(optarg);
            break;
        case 261:
            im->step =  atoi(optarg);
            break;
+       case 262:
+           im->gridfit = 0;
+           break;
        case 's':
            if ((parsetime_error = parsetime(optarg, &start_tv))) {
                rrd_set_error( "start time: %s", parsetime_error );
        case 's':
            if ((parsetime_error = parsetime(optarg, &start_tv))) {
                rrd_set_error( "start time: %s", parsetime_error );
index de73a72..6fac38d 100644 (file)
@@ -66,6 +66,12 @@ typedef struct xlab_t {
     char         *stst;        /* strftime string*/
 } xlab_t;
 
     char         *stst;        /* strftime string*/
 } xlab_t;
 
+typedef struct ygrid_scale_t {  /* y axis grid scaling info */
+    double       gridstep;
+    int          labfact;
+    char         labfmt[64];
+} ygrid_scale_t;
+
 /* sensible y label intervals ...*/
 
 typedef struct ylab_t {
 /* sensible y label intervals ...*/
 
 typedef struct ylab_t {
@@ -134,6 +140,9 @@ typedef struct image_desc_t {
     rrd_value_t    minval,maxval;  /* extreme values in the data */
     int            rigid;          /* do not expand range even with 
                                      values outside */
     rrd_value_t    minval,maxval;  /* extreme values in the data */
     int            rigid;          /* do not expand range even with 
                                      values outside */
+    ygrid_scale_t  ygrid_scale;    /* calculated y axis grid info */
+    int            gridfit;        /* adjust y-axis range etc so all
+                                     grindlines falls in integer pixel values */
     char*          imginfo;        /* construct an <IMG ... tag and return 
                                      as first retval */
     int            lazy;           /* only update the image if there is
     char*          imginfo;        /* construct an <IMG ... tag and return 
                                      as first retval */
     int            lazy;           /* only update the image if there is
@@ -161,7 +170,7 @@ typedef struct image_desc_t {
 
 /* Prototypes */
 int xtr(image_desc_t *,time_t);
 
 /* Prototypes */
 int xtr(image_desc_t *,time_t);
-int ytr(image_desc_t *, double);
+double ytr(image_desc_t *, double);
 enum gf_en gf_conv(char *);
 enum gfx_if_en if_conv(char *);
 enum tmt_en tmt_conv(char *);
 enum gf_en gf_conv(char *);
 enum gfx_if_en if_conv(char *);
 enum tmt_en tmt_conv(char *);
@@ -171,6 +180,7 @@ int im_free(image_desc_t *);
 void auto_scale( image_desc_t *,  double *, char **, double *);
 void si_unit( image_desc_t *);
 void expand_range(image_desc_t *);
 void auto_scale( image_desc_t *,  double *, char **, double *);
 void si_unit( image_desc_t *);
 void expand_range(image_desc_t *);
+void apply_gridfit(image_desc_t *);
 void reduce_data( enum cf_en,  unsigned long,  time_t *, time_t *,  unsigned long *,  unsigned long *,  rrd_value_t **);
 int data_fetch( image_desc_t *);
 long find_var(image_desc_t *, char *);
 void reduce_data( enum cf_en,  unsigned long,  time_t *, time_t *,  unsigned long *,  unsigned long *,  rrd_value_t **);
 int data_fetch( image_desc_t *);
 long find_var(image_desc_t *, char *);
@@ -182,7 +192,8 @@ time_t find_first_time( time_t,  enum tmt_en,  long);
 time_t find_next_time( time_t,  enum tmt_en,  long);
 int print_calc(image_desc_t *, char ***);
 int leg_place(image_desc_t *);
 time_t find_next_time( time_t,  enum tmt_en,  long);
 int print_calc(image_desc_t *, char ***);
 int leg_place(image_desc_t *);
-int horizontal_grid(image_desc_t *);
+int calc_horizontal_grid(image_desc_t *);
+int draw_horizontal_grid(image_desc_t *);
 int horizontal_log_grid(image_desc_t *);
 void vertical_grid(image_desc_t *);
 void axis_paint(image_desc_t *);
 int horizontal_log_grid(image_desc_t *);
 void vertical_grid(image_desc_t *);
 void axis_paint(image_desc_t *);