From dd7a80457423c23c6d6d9cc05b46ced2a3ec2dc6 Mon Sep 17 00:00:00 2001 From: oetiker Date: Mon, 15 Apr 2002 21:29:24 +0000 Subject: [PATCH] Evenly-spaced y-axis gridlines. 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 git-svn-id: svn://svn.oetiker.ch/rrdtool/trunk/program@123 a5681a0c-68f1-0310-ab6d-d61299d08faa --- src/rrd_gfx.c | 33 +++++++++-- src/rrd_graph.c | 174 ++++++++++++++++++++++++++++++++++++++++---------------- src/rrd_graph.h | 15 ++++- 3 files changed, 166 insertions(+), 56 deletions(-) diff --git a/src/rrd_gfx.c b/src/rrd_gfx.c index 5fd0060..4ddd584 100644 --- a/src/rrd_gfx.c +++ b/src/rrd_gfx.c @@ -169,6 +169,8 @@ int gfx_add_point (gfx_node_t *node, 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 */ @@ -280,6 +282,29 @@ double gfx_get_text_width_libart ( gfx_canvas_t *canvas, 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 */ @@ -306,13 +331,11 @@ int gfx_render_png (gfx_canvas_t *canvas, 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); + 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, diff --git a/src/rrd_graph.c b/src/rrd_graph.c index c58326f..2e6cb9f 100644 --- a/src/rrd_graph.c +++ b/src/rrd_graph.c @@ -129,7 +129,7 @@ xtr(image_desc_t *im,time_t mytime){ } /* translate data values into y coordinates */ -int +double ytr(image_desc_t *im, double value){ static double pixie; double yval; @@ -140,26 +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) { - yval = im->yorigin - pixie * (value - im->minval) + 0.5; + yval = im->yorigin - pixie * (value - im->minval); } else { if (value < im->minval) { yval = im->yorigin; } else { - yval = im->yorigin - pixie * (log10(value) - log10(im->minval)) + 0.5; + yval = im->yorigin - pixie * (log10(value) - log10(im->minval)); } } /* make sure we don't return anything too unreasonable. GD lib can get terribly slow when drawing lines outside its scope. This is especially problematic in connection with the rigid option */ if (! im->rigid) { - return (int)yval; - } else if ((int)yval > im->yorigin) { - return im->yorigin+2; - } else if ((int) yval < im->yorigin - im->ysize){ - return im->yorigin - im->ysize - 2; - } else { - return (int)yval; + /* keep yval as-is */ + } else if (yval > im->yorigin) { + yval = im->yorigin+2; + } else if (yval < im->yorigin - im->ysize){ + yval = im->yorigin - im->ysize - 2; } + return yval; } @@ -438,7 +437,73 @@ expand_range(image_desc_t *im) #endif } - +void +apply_gridfit(image_desc_t *im) +{ + if (isnan(im->minval) || isnan(im->maxval)) + return; + ytr(im,DNAN); + if (im->logarithmic) { + double ya, yb, ypix, ypixfrac; + double log10_range = log10(im->maxval) - log10(im->minval); + ya = pow((double)10, floor(log10(im->minval))); + while (ya < im->minval) + ya *= 10; + if (ya > im->maxval) + return; /* don't have y=10^x gridline */ + yb = ya * 10; + if (yb <= im->maxval) { + /* we have at least 2 y=10^x gridlines. + Make sure distance between them in pixels + are an integer by expanding im->maxval */ + double y_pixel_delta = ytr(im, ya) - ytr(im, yb); + double factor = y_pixel_delta / floor(y_pixel_delta); + double new_log10_range = factor * log10_range; + double new_ymax_log10 = log10(im->minval) + new_log10_range; + im->maxval = pow(10, new_ymax_log10); + ytr(im, DNAN); /* reset precalc */ + log10_range = log10(im->maxval) - log10(im->minval); + } + /* make sure first y=10^x gridline is located on + integer pixel position by moving scale slightly + downwards (sub-pixel movement) */ + ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */ + ypixfrac = ypix - floor(ypix); + if (ypixfrac > 0 && ypixfrac < 1) { + double yfrac = ypixfrac / im->ysize; + im->minval = pow(10, log10(im->minval) - yfrac * log10_range); + im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range); + ytr(im, DNAN); /* reset precalc */ + } + } else { + /* Make sure we have an integer pixel distance between + each minor gridline */ + double ypos1 = ytr(im, im->minval); + double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep); + double y_pixel_delta = ypos1 - ypos2; + double factor = y_pixel_delta / floor(y_pixel_delta); + double new_range = factor * (im->maxval - im->minval); + double gridstep = im->ygrid_scale.gridstep; + double minor_y, minor_y_px, minor_y_px_frac; + im->maxval = im->minval + new_range; + ytr(im, DNAN); /* reset precalc */ + /* make sure first minor gridline is on integer pixel y coord */ + minor_y = gridstep * floor(im->minval / gridstep); + while (minor_y < im->minval) + minor_y += gridstep; + minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */ + minor_y_px_frac = minor_y_px - floor(minor_y_px); + if (minor_y_px_frac > 0 && minor_y_px_frac < 1) { + double yfrac = minor_y_px_frac / im->ysize; + double range = im->maxval - im->minval; + im->minval = im->minval - yfrac * range; + im->maxval = im->maxval - yfrac * range; + ytr(im, DNAN); /* reset precalc */ + } + calc_horizontal_grid(im); /* recalc with changed im->maxval */ + } +} + /* reduce data reimplementation by Alex */ void @@ -1385,21 +1450,15 @@ leg_place(image_desc_t *im) int -horizontal_grid(image_desc_t *im) +calc_horizontal_grid(image_desc_t *im) { double range; double scaledrange; int pixel,i; - int sgrid,egrid; - double gridstep; - double scaledstep; - char graph_label[100]; - double X0,X1,Y0; - int labfact,gridind; + int gridind; int decimals, fractionals; - char labfmt[64]; - labfact=2; + im->ygrid_scale.labfact=2; gridind=-1; range = im->maxval - im->minval; scaledrange = range / im->magfact; @@ -1421,25 +1480,25 @@ horizontal_grid(image_desc_t *im) fractionals = floor(log10(range)); if(fractionals < 0) /* small amplitude. */ - sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1); + sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1); else - sprintf(labfmt, "%%%d.1f", decimals + 1); - gridstep = pow((double)10, (double)fractionals); - if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */ - gridstep = 0.1; + sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1); + im->ygrid_scale.gridstep = pow((double)10, (double)fractionals); + if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */ + im->ygrid_scale.gridstep = 0.1; /* should have at least 5 lines but no more then 15 */ - if(range/gridstep < 5) - gridstep /= 10; - if(range/gridstep > 15) - gridstep *= 10; - if(range/gridstep > 5) { - labfact = 1; - if(range/gridstep > 8) - labfact = 2; + if(range/im->ygrid_scale.gridstep < 5) + im->ygrid_scale.gridstep /= 10; + if(range/im->ygrid_scale.gridstep > 15) + im->ygrid_scale.gridstep *= 10; + if(range/im->ygrid_scale.gridstep > 5) { + im->ygrid_scale.labfact = 1; + if(range/im->ygrid_scale.gridstep > 8) + im->ygrid_scale.labfact = 2; } else { - gridstep /= 5; - labfact = 5; + im->ygrid_scale.gridstep /= 5; + im->ygrid_scale.labfact = 5; } } else { @@ -1453,33 +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) { - labfact = ylab[gridind].lfac[i]; + im->ygrid_scale.labfact = ylab[gridind].lfac[i]; break; } } - gridstep = ylab[gridind].grid * im->magfact; + im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact; } } else { - gridstep = im->ygridstep; - labfact = im->ylabfact; + im->ygrid_scale.gridstep = im->ygridstep; + im->ygrid_scale.labfact = im->ylabfact; } - - X0=im->xorigin; - X1=im->xorigin+im->xsize; + return 1; +} + +int draw_horizontal_grid(image_desc_t *im) +{ + int i; + double scaledstep; + char graph_label[100]; + double X0=im->xorigin; + double X1=im->xorigin+im->xsize; - sgrid = (int)( im->minval / gridstep - 1); - egrid = (int)( im->maxval / gridstep + 1); - scaledstep = gridstep/im->magfact; + int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1); + int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1); + scaledstep = im->ygrid_scale.gridstep/im->magfact; for (i = sgrid; i <= egrid; i++){ - Y0=ytr(im,gridstep*i); + double Y0=ytr(im,im->ygrid_scale.gridstep*i); if ( Y0 >= im->yorigin-im->ysize && Y0 <= im->yorigin){ - if(i % labfact == 0){ + if(i % im->ygrid_scale.labfact == 0){ if (i==0 || im->symbol == ' ') { if(scaledstep < 1){ if(im->extra_flags & ALTYGRID) { - sprintf(graph_label,labfmt,scaledstep*i); + sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i); } else { sprintf(graph_label,"%4.1f",scaledstep*i); @@ -1757,7 +1823,7 @@ grid_paint(image_desc_t *im) if(im->logarithmic){ res = horizontal_log_grid(im); } else { - res = horizontal_grid(im); + res = draw_horizontal_grid(im); } /* dont draw horizontal grid if there is no min and max val */ @@ -2135,6 +2201,11 @@ graph_paint(image_desc_t *im, char ***calcpr) expand_range(im); /* make sure the upper and lower limit are sensible values */ + if (!calc_horizontal_grid(im)) + return -1; + if (im->gridfit) + apply_gridfit(im); + /************************************************************** *** Calculating sizes and locations became a bit confusing *** *** so I moved this into a separate function. *** @@ -2521,6 +2592,7 @@ rrd_graph_init(image_desc_t *im) im->unitsexponent= 9999; im->extra_flags= 0; im->rigid = 0; + im->gridfit = 1; im->imginfo = NULL; im->lazy = 0; im->logarithmic = 0; @@ -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}, + {"no-gridfit", no_argument, 0, 262}, {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 262: + im->gridfit = 0; + break; case 's': if ((parsetime_error = parsetime(optarg, &start_tv))) { rrd_set_error( "start time: %s", parsetime_error ); diff --git a/src/rrd_graph.h b/src/rrd_graph.h index de73a72..6fac38d 100644 --- a/src/rrd_graph.h +++ b/src/rrd_graph.h @@ -66,6 +66,12 @@ typedef struct 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 { @@ -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 */ + 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