1 /****************************************************************************
2 * RRDtool 1.2.1 Copyright by Tobi Oetiker, 1997-2005
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
12 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
25 #include "rrd_graph.h"
27 /* some constant definitions */
31 #ifndef RRD_DEFAULT_FONT
32 /* there is special code later to pick Cour.ttf when running on windows */
33 #define RRD_DEFAULT_FONT "DejaVuSansMono-Roman.ttf"
36 text_prop_t text_prop[] = {
37 { 8.0, RRD_DEFAULT_FONT }, /* default */
38 { 9.0, RRD_DEFAULT_FONT }, /* title */
39 { 7.0, RRD_DEFAULT_FONT }, /* axis */
40 { 8.0, RRD_DEFAULT_FONT }, /* unit */
41 { 8.0, RRD_DEFAULT_FONT } /* legend */
45 {0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
46 {2, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
47 {5, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
48 {10, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
49 {30, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
50 {60, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
51 {180, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
52 /*{300, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
53 {600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
54 {1800, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
55 {3600, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
56 {3*3600, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
57 {6*3600, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
58 {48*3600, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
59 {10*24*3600, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
60 {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
63 /* sensible logarithmic y label intervals ...
64 the first element of each row defines the possible starting points on the
65 y axis ... the other specify the */
67 double yloglab[][12]= {{ 1e9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
68 { 1e3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
69 { 1e1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
70 /* { 1e1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, */
71 { 1e1, 1, 2.5, 5, 7.5, 0, 0, 0, 0, 0, 0, 0 },
72 { 1e1, 1, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
73 { 1e1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0 },
74 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }};
76 /* sensible y label intervals ...*/
94 gfx_color_t graph_col[] = /* default colors */
95 { 0xFFFFFFFF, /* canvas */
96 0xF0F0F0FF, /* background */
97 0xD0D0D0FF, /* shade A */
98 0xA0A0A0FF, /* shade B */
99 0x90909080, /* grid */
100 0xE0505080, /* major grid */
101 0x000000FF, /* font */
102 0x802020FF, /* arrow */
103 0x202020FF /* axis */
110 # define DPRINT(x) (void)(printf x, printf("\n"))
116 /* initialize with xtr(im,0); */
118 xtr(image_desc_t *im,time_t mytime){
121 pixie = (double) im->xsize / (double)(im->end - im->start);
124 return (int)((double)im->xorigin
125 + pixie * ( mytime - im->start ) );
128 /* translate data values into y coordinates */
130 ytr(image_desc_t *im, double value){
135 pixie = (double) im->ysize / (im->maxval - im->minval);
137 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
139 } else if(!im->logarithmic) {
140 yval = im->yorigin - pixie * (value - im->minval);
142 if (value < im->minval) {
145 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
148 /* make sure we don't return anything too unreasonable. GD lib can
149 get terribly slow when drawing lines outside its scope. This is
150 especially problematic in connection with the rigid option */
152 /* keep yval as-is */
153 } else if (yval > im->yorigin) {
155 } else if (yval < im->yorigin - im->ysize){
156 yval = im->yorigin - im->ysize;
163 /* conversion function for symbolic entry names */
166 #define conv_if(VV,VVV) \
167 if (strcmp(#VV, string) == 0) return VVV ;
169 enum gf_en gf_conv(char *string){
171 conv_if(PRINT,GF_PRINT)
172 conv_if(GPRINT,GF_GPRINT)
173 conv_if(COMMENT,GF_COMMENT)
174 conv_if(HRULE,GF_HRULE)
175 conv_if(VRULE,GF_VRULE)
176 conv_if(LINE,GF_LINE)
177 conv_if(AREA,GF_AREA)
178 conv_if(STACK,GF_STACK)
179 conv_if(TICK,GF_TICK)
181 conv_if(CDEF,GF_CDEF)
182 conv_if(VDEF,GF_VDEF)
184 conv_if(PART,GF_PART)
186 conv_if(XPORT,GF_XPORT)
187 conv_if(SHIFT,GF_SHIFT)
192 enum gfx_if_en if_conv(char *string){
202 enum tmt_en tmt_conv(char *string){
204 conv_if(SECOND,TMT_SECOND)
205 conv_if(MINUTE,TMT_MINUTE)
206 conv_if(HOUR,TMT_HOUR)
208 conv_if(WEEK,TMT_WEEK)
209 conv_if(MONTH,TMT_MONTH)
210 conv_if(YEAR,TMT_YEAR)
214 enum grc_en grc_conv(char *string){
216 conv_if(BACK,GRC_BACK)
217 conv_if(CANVAS,GRC_CANVAS)
218 conv_if(SHADEA,GRC_SHADEA)
219 conv_if(SHADEB,GRC_SHADEB)
220 conv_if(GRID,GRC_GRID)
221 conv_if(MGRID,GRC_MGRID)
222 conv_if(FONT,GRC_FONT)
223 conv_if(ARROW,GRC_ARROW)
224 conv_if(AXIS,GRC_AXIS)
229 enum text_prop_en text_prop_conv(char *string){
231 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
232 conv_if(TITLE,TEXT_PROP_TITLE)
233 conv_if(AXIS,TEXT_PROP_AXIS)
234 conv_if(UNIT,TEXT_PROP_UNIT)
235 conv_if(LEGEND,TEXT_PROP_LEGEND)
243 im_free(image_desc_t *im)
247 if (im == NULL) return 0;
248 for(i=0;i<(unsigned)im->gdes_c;i++){
249 if (im->gdes[i].data_first){
250 /* careful here, because a single pointer can occur several times */
251 free (im->gdes[i].data);
252 if (im->gdes[i].ds_namv){
253 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
254 free(im->gdes[i].ds_namv[ii]);
255 free(im->gdes[i].ds_namv);
258 free (im->gdes[i].p_data);
259 free (im->gdes[i].rpnp);
262 gfx_destroy(im->canvas);
266 /* find SI magnitude symbol for the given number*/
269 image_desc_t *im, /* image description */
276 char *symbol[] = {"a", /* 10e-18 Atto */
277 "f", /* 10e-15 Femto */
278 "p", /* 10e-12 Pico */
279 "n", /* 10e-9 Nano */
280 "u", /* 10e-6 Micro */
281 "m", /* 10e-3 Milli */
286 "T", /* 10e12 Tera */
287 "P", /* 10e15 Peta */
293 if (*value == 0.0 || isnan(*value) ) {
297 sindex = floor(log(fabs(*value))/log((double)im->base));
298 *magfact = pow((double)im->base, (double)sindex);
299 (*value) /= (*magfact);
301 if ( sindex <= symbcenter && sindex >= -symbcenter) {
302 (*symb_ptr) = symbol[sindex+symbcenter];
310 /* find SI magnitude symbol for the numbers on the y-axis*/
313 image_desc_t *im /* image description */
317 char symbol[] = {'a', /* 10e-18 Atto */
318 'f', /* 10e-15 Femto */
319 'p', /* 10e-12 Pico */
320 'n', /* 10e-9 Nano */
321 'u', /* 10e-6 Micro */
322 'm', /* 10e-3 Milli */
327 'T', /* 10e12 Tera */
328 'P', /* 10e15 Peta */
332 double digits,viewdigits=0;
334 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
336 if (im->unitsexponent != 9999) {
337 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
338 viewdigits = floor(im->unitsexponent / 3);
343 im->magfact = pow((double)im->base , digits);
346 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
349 im->viewfactor = im->magfact / pow((double)im->base , viewdigits);
351 pow((double)im->base , viewdigits);
353 if ( ((viewdigits+symbcenter) < sizeof(symbol)) &&
354 ((viewdigits+symbcenter) >= 0) )
355 im->symbol = symbol[(int)viewdigits+symbcenter];
360 /* move min and max values around to become sensible */
363 expand_range(image_desc_t *im)
365 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
366 600.0,500.0,400.0,300.0,250.0,
367 200.0,125.0,100.0,90.0,80.0,
368 75.0,70.0,60.0,50.0,40.0,30.0,
369 25.0,20.0,10.0,9.0,8.0,
370 7.0,6.0,5.0,4.0,3.5,3.0,
371 2.5,2.0,1.8,1.5,1.2,1.0,
372 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
374 double scaled_min,scaled_max;
381 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
382 im->minval,im->maxval,im->magfact);
385 if (isnan(im->ygridstep)){
386 if(im->extra_flags & ALTAUTOSCALE) {
387 /* measure the amplitude of the function. Make sure that
388 graph boundaries are slightly higher then max/min vals
389 so we can see amplitude on the graph */
392 delt = im->maxval - im->minval;
394 fact = 2.0 * pow(10.0,
395 floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
397 adj = (fact - delt) * 0.55;
399 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
405 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
406 /* measure the amplitude of the function. Make sure that
407 graph boundaries are slightly higher than max vals
408 so we can see amplitude on the graph */
409 adj = (im->maxval - im->minval) * 0.1;
413 scaled_min = im->minval / im->magfact;
414 scaled_max = im->maxval / im->magfact;
416 for (i=1; sensiblevalues[i] > 0; i++){
417 if (sensiblevalues[i-1]>=scaled_min &&
418 sensiblevalues[i]<=scaled_min)
419 im->minval = sensiblevalues[i]*(im->magfact);
421 if (-sensiblevalues[i-1]<=scaled_min &&
422 -sensiblevalues[i]>=scaled_min)
423 im->minval = -sensiblevalues[i-1]*(im->magfact);
425 if (sensiblevalues[i-1] >= scaled_max &&
426 sensiblevalues[i] <= scaled_max)
427 im->maxval = sensiblevalues[i-1]*(im->magfact);
429 if (-sensiblevalues[i-1]<=scaled_max &&
430 -sensiblevalues[i] >=scaled_max)
431 im->maxval = -sensiblevalues[i]*(im->magfact);
433 /* no sensiblevalues found. we switch to ALTYGRID mode */
434 if (sensiblevalues[i] == 0){
435 im->extra_flags |= ALTYGRID;
439 /* adjust min and max to the grid definition if there is one */
440 im->minval = (double)im->ylabfact * im->ygridstep *
441 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
442 im->maxval = (double)im->ylabfact * im->ygridstep *
443 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
447 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
448 im->minval,im->maxval,im->magfact);
453 apply_gridfit(image_desc_t *im)
455 if (isnan(im->minval) || isnan(im->maxval))
458 if (im->logarithmic) {
459 double ya, yb, ypix, ypixfrac;
460 double log10_range = log10(im->maxval) - log10(im->minval);
461 ya = pow((double)10, floor(log10(im->minval)));
462 while (ya < im->minval)
465 return; /* don't have y=10^x gridline */
467 if (yb <= im->maxval) {
468 /* we have at least 2 y=10^x gridlines.
469 Make sure distance between them in pixels
470 are an integer by expanding im->maxval */
471 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
472 double factor = y_pixel_delta / floor(y_pixel_delta);
473 double new_log10_range = factor * log10_range;
474 double new_ymax_log10 = log10(im->minval) + new_log10_range;
475 im->maxval = pow(10, new_ymax_log10);
476 ytr(im, DNAN); /* reset precalc */
477 log10_range = log10(im->maxval) - log10(im->minval);
479 /* make sure first y=10^x gridline is located on
480 integer pixel position by moving scale slightly
481 downwards (sub-pixel movement) */
482 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
483 ypixfrac = ypix - floor(ypix);
484 if (ypixfrac > 0 && ypixfrac < 1) {
485 double yfrac = ypixfrac / im->ysize;
486 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
487 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
488 ytr(im, DNAN); /* reset precalc */
491 /* Make sure we have an integer pixel distance between
492 each minor gridline */
493 double ypos1 = ytr(im, im->minval);
494 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
495 double y_pixel_delta = ypos1 - ypos2;
496 double factor = y_pixel_delta / floor(y_pixel_delta);
497 double new_range = factor * (im->maxval - im->minval);
498 double gridstep = im->ygrid_scale.gridstep;
499 double minor_y, minor_y_px, minor_y_px_frac;
500 im->maxval = im->minval + new_range;
501 ytr(im, DNAN); /* reset precalc */
502 /* make sure first minor gridline is on integer pixel y coord */
503 minor_y = gridstep * floor(im->minval / gridstep);
504 while (minor_y < im->minval)
506 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
507 minor_y_px_frac = minor_y_px - floor(minor_y_px);
508 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
509 double yfrac = minor_y_px_frac / im->ysize;
510 double range = im->maxval - im->minval;
511 im->minval = im->minval - yfrac * range;
512 im->maxval = im->maxval - yfrac * range;
513 ytr(im, DNAN); /* reset precalc */
515 calc_horizontal_grid(im); /* recalc with changed im->maxval */
519 /* reduce data reimplementation by Alex */
523 enum cf_en cf, /* which consolidation function ?*/
524 unsigned long cur_step, /* step the data currently is in */
525 time_t *start, /* start, end and step as requested ... */
526 time_t *end, /* ... by the application will be ... */
527 unsigned long *step, /* ... adjusted to represent reality */
528 unsigned long *ds_cnt, /* number of data sources in file */
529 rrd_value_t **data) /* two dimensional array containing the data */
531 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
532 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
533 rrd_value_t *srcptr,*dstptr;
535 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
538 row_cnt = ((*end)-(*start))/cur_step;
544 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
545 row_cnt,reduce_factor,*start,*end,cur_step);
546 for (col=0;col<row_cnt;col++) {
547 printf("time %10lu: ",*start+(col+1)*cur_step);
548 for (i=0;i<*ds_cnt;i++)
549 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
554 /* We have to combine [reduce_factor] rows of the source
555 ** into one row for the destination. Doing this we also
556 ** need to take care to combine the correct rows. First
557 ** alter the start and end time so that they are multiples
558 ** of the new step time. We cannot reduce the amount of
559 ** time so we have to move the end towards the future and
560 ** the start towards the past.
562 end_offset = (*end) % (*step);
563 start_offset = (*start) % (*step);
565 /* If there is a start offset (which cannot be more than
566 ** one destination row), skip the appropriate number of
567 ** source rows and one destination row. The appropriate
568 ** number is what we do know (start_offset/cur_step) of
569 ** the new interval (*step/cur_step aka reduce_factor).
572 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
573 printf("row_cnt before: %lu\n",row_cnt);
576 (*start) = (*start)-start_offset;
577 skiprows=reduce_factor-start_offset/cur_step;
578 srcptr+=skiprows* *ds_cnt;
579 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
583 printf("row_cnt between: %lu\n",row_cnt);
586 /* At the end we have some rows that are not going to be
587 ** used, the amount is end_offset/cur_step
590 (*end) = (*end)-end_offset+(*step);
591 skiprows = end_offset/cur_step;
595 printf("row_cnt after: %lu\n",row_cnt);
598 /* Sanity check: row_cnt should be multiple of reduce_factor */
599 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
601 if (row_cnt%reduce_factor) {
602 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
603 row_cnt,reduce_factor);
604 printf("BUG in reduce_data()\n");
608 /* Now combine reduce_factor intervals at a time
609 ** into one interval for the destination.
612 for (dst_row=0;(long int)row_cnt>=reduce_factor;dst_row++) {
613 for (col=0;col<(*ds_cnt);col++) {
614 rrd_value_t newval=DNAN;
615 unsigned long validval=0;
617 for (i=0;i<reduce_factor;i++) {
618 if (isnan(srcptr[i*(*ds_cnt)+col])) {
622 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
630 newval += srcptr[i*(*ds_cnt)+col];
633 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
636 /* an interval contains a failure if any subintervals contained a failure */
638 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
641 newval = srcptr[i*(*ds_cnt)+col];
646 if (validval == 0){newval = DNAN;} else{
664 srcptr+=(*ds_cnt)*reduce_factor;
665 row_cnt-=reduce_factor;
667 /* If we had to alter the endtime, we didn't have enough
668 ** source rows to fill the last row. Fill it with NaN.
670 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
672 row_cnt = ((*end)-(*start))/ *step;
674 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
675 row_cnt,*start,*end,*step);
676 for (col=0;col<row_cnt;col++) {
677 printf("time %10lu: ",*start+(col+1)*(*step));
678 for (i=0;i<*ds_cnt;i++)
679 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
686 /* get the data required for the graphs from the
690 data_fetch(image_desc_t *im )
695 /* pull the data from the log files ... */
696 for (i=0;i< (int)im->gdes_c;i++){
697 /* only GF_DEF elements fetch data */
698 if (im->gdes[i].gf != GF_DEF)
702 /* do we have it already ?*/
703 for (ii=0;ii<i;ii++) {
704 if (im->gdes[ii].gf != GF_DEF)
706 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
707 && (im->gdes[i].cf == im->gdes[ii].cf)
708 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
709 && (im->gdes[i].start == im->gdes[ii].start)
710 && (im->gdes[i].end == im->gdes[ii].end)
711 && (im->gdes[i].step == im->gdes[ii].step)) {
712 /* OK, the data is already there.
713 ** Just copy the header portion
715 im->gdes[i].start = im->gdes[ii].start;
716 im->gdes[i].end = im->gdes[ii].end;
717 im->gdes[i].step = im->gdes[ii].step;
718 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
719 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
720 im->gdes[i].data = im->gdes[ii].data;
721 im->gdes[i].data_first = 0;
728 unsigned long ft_step = im->gdes[i].step ;
730 if((rrd_fetch_fn(im->gdes[i].rrd,
736 &im->gdes[i].ds_namv,
737 &im->gdes[i].data)) == -1){
740 im->gdes[i].data_first = 1;
741 im->gdes[i].step = im->step;
743 if (ft_step < im->gdes[i].step) {
744 reduce_data(im->gdes[i].cf_reduce,
752 im->gdes[i].step = ft_step;
756 /* lets see if the required data source is really there */
757 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
758 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
761 if (im->gdes[i].ds== -1){
762 rrd_set_error("No DS called '%s' in '%s'",
763 im->gdes[i].ds_nam,im->gdes[i].rrd);
771 /* evaluate the expressions in the CDEF functions */
773 /*************************************************************
775 *************************************************************/
778 find_var_wrapper(void *arg1, char *key)
780 return find_var((image_desc_t *) arg1, key);
783 /* find gdes containing var*/
785 find_var(image_desc_t *im, char *key){
787 for(ii=0;ii<im->gdes_c-1;ii++){
788 if((im->gdes[ii].gf == GF_DEF
789 || im->gdes[ii].gf == GF_VDEF
790 || im->gdes[ii].gf == GF_CDEF)
791 && (strcmp(im->gdes[ii].vname,key) == 0)){
798 /* find the largest common denominator for all the numbers
799 in the 0 terminated num array */
804 for (i=0;num[i+1]!=0;i++){
806 rest=num[i] % num[i+1];
807 num[i]=num[i+1]; num[i+1]=rest;
811 /* return i==0?num[i]:num[i-1]; */
815 /* run the rpn calculator on all the VDEF and CDEF arguments */
817 data_calc( image_desc_t *im){
821 long *steparray, rpi;
826 rpnstack_init(&rpnstack);
828 for (gdi=0;gdi<im->gdes_c;gdi++){
829 /* Look for GF_VDEF and GF_CDEF in the same loop,
830 * so CDEFs can use VDEFs and vice versa
832 switch (im->gdes[gdi].gf) {
836 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
838 /* remove current shift */
839 vdp->start -= vdp->shift;
840 vdp->end -= vdp->shift;
843 if (im->gdes[gdi].shidx >= 0)
844 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
847 vdp->shift = im->gdes[gdi].shval;
849 /* normalize shift to multiple of consolidated step */
850 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
853 vdp->start += vdp->shift;
854 vdp->end += vdp->shift;
858 /* A VDEF has no DS. This also signals other parts
859 * of rrdtool that this is a VDEF value, not a CDEF.
861 im->gdes[gdi].ds_cnt = 0;
862 if (vdef_calc(im,gdi)) {
863 rrd_set_error("Error processing VDEF '%s'"
866 rpnstack_free(&rpnstack);
871 im->gdes[gdi].ds_cnt = 1;
872 im->gdes[gdi].ds = 0;
873 im->gdes[gdi].data_first = 1;
874 im->gdes[gdi].start = 0;
875 im->gdes[gdi].end = 0;
880 /* Find the variables in the expression.
881 * - VDEF variables are substituted by their values
882 * and the opcode is changed into OP_NUMBER.
883 * - CDEF variables are analized for their step size,
884 * the lowest common denominator of all the step
885 * sizes of the data sources involved is calculated
886 * and the resulting number is the step size for the
887 * resulting data source.
889 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
890 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
891 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
892 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
893 if (im->gdes[ptr].ds_cnt == 0) {
895 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
897 im->gdes[ptr].vname);
898 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
900 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
901 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
904 rrd_realloc(steparray,
905 (++stepcnt+1)*sizeof(*steparray)))==NULL){
906 rrd_set_error("realloc steparray");
907 rpnstack_free(&rpnstack);
911 steparray[stepcnt-1] = im->gdes[ptr].step;
913 /* adjust start and end of cdef (gdi) so
914 * that it runs from the latest start point
915 * to the earliest endpoint of any of the
916 * rras involved (ptr)
918 if(im->gdes[gdi].start < im->gdes[ptr].start)
919 im->gdes[gdi].start = im->gdes[ptr].start;
921 if(im->gdes[gdi].end == 0 ||
922 im->gdes[gdi].end > im->gdes[ptr].end)
923 im->gdes[gdi].end = im->gdes[ptr].end;
925 /* store pointer to the first element of
926 * the rra providing data for variable,
927 * further save step size and data source
930 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
931 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
932 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
934 /* backoff the *.data ptr; this is done so
935 * rpncalc() function doesn't have to treat
936 * the first case differently
938 } /* if ds_cnt != 0 */
939 } /* if OP_VARIABLE */
940 } /* loop through all rpi */
942 /* move the data pointers to the correct period */
943 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
944 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
945 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
946 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
947 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
950 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
954 if(steparray == NULL){
955 rrd_set_error("rpn expressions without DEF"
956 " or CDEF variables are not supported");
957 rpnstack_free(&rpnstack);
960 steparray[stepcnt]=0;
961 /* Now find the resulting step. All steps in all
962 * used RRAs have to be visited
964 im->gdes[gdi].step = lcd(steparray);
966 if((im->gdes[gdi].data = malloc((
967 (im->gdes[gdi].end-im->gdes[gdi].start)
968 / im->gdes[gdi].step)
969 * sizeof(double)))==NULL){
970 rrd_set_error("malloc im->gdes[gdi].data");
971 rpnstack_free(&rpnstack);
975 /* Step through the new cdef results array and
976 * calculate the values
978 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
979 now<=im->gdes[gdi].end;
980 now += im->gdes[gdi].step)
982 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
984 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
985 * in this case we are advancing by timesteps;
986 * we use the fact that time_t is a synonym for long
988 if (rpn_calc(rpnp,&rpnstack,(long) now,
989 im->gdes[gdi].data,++dataidx) == -1) {
990 /* rpn_calc sets the error string */
991 rpnstack_free(&rpnstack);
994 } /* enumerate over time steps within a CDEF */
999 } /* enumerate over CDEFs */
1000 rpnstack_free(&rpnstack);
1004 /* massage data so, that we get one value for each x coordinate in the graph */
1006 data_proc( image_desc_t *im ){
1008 double pixstep = (double)(im->end-im->start)
1009 /(double)im->xsize; /* how much time
1010 passes in one pixel */
1012 double minval=DNAN,maxval=DNAN;
1014 unsigned long gr_time;
1016 /* memory for the processed data */
1017 for(i=0;i<im->gdes_c;i++) {
1018 if((im->gdes[i].gf==GF_LINE) ||
1019 (im->gdes[i].gf==GF_AREA) ||
1020 (im->gdes[i].gf==GF_TICK) ||
1021 (im->gdes[i].gf==GF_STACK)) {
1022 if((im->gdes[i].p_data = malloc((im->xsize +1)
1023 * sizeof(rrd_value_t)))==NULL){
1024 rrd_set_error("malloc data_proc");
1030 for (i=0;i<im->xsize;i++) { /* for each pixel */
1032 gr_time = im->start+pixstep*i; /* time of the current step */
1035 for (ii=0;ii<im->gdes_c;ii++) {
1037 switch (im->gdes[ii].gf) {
1041 if (!im->gdes[ii].stack)
1044 value = im->gdes[ii].yrule;
1045 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1046 /* The time of the data doesn't necessarily match
1047 ** the time of the graph. Beware.
1049 vidx = im->gdes[ii].vidx;
1050 if (im->gdes[vidx].gf == GF_VDEF) {
1051 value = im->gdes[vidx].vf.val;
1052 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1053 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1054 value = im->gdes[vidx].data[
1055 (unsigned long) floor(
1056 (double)(gr_time - im->gdes[vidx].start)
1057 / im->gdes[vidx].step)
1058 * im->gdes[vidx].ds_cnt
1066 if (! isnan(value)) {
1068 im->gdes[ii].p_data[i] = paintval;
1069 /* GF_TICK: the data values are not
1070 ** relevant for min and max
1072 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1073 if (isnan(minval) || paintval < minval)
1075 if (isnan(maxval) || paintval > maxval)
1079 im->gdes[ii].p_data[i] = DNAN;
1088 /* if min or max have not been asigned a value this is because
1089 there was no data in the graph ... this is not good ...
1090 lets set these to dummy values then ... */
1092 if (isnan(minval)) minval = 0.0;
1093 if (isnan(maxval)) maxval = 1.0;
1095 /* adjust min and max values */
1096 if (isnan(im->minval)
1097 /* don't adjust low-end with log scale */
1098 || ((!im->logarithmic && !im->rigid) && im->minval > minval)
1100 im->minval = minval;
1101 if (isnan(im->maxval)
1102 || (!im->rigid && im->maxval < maxval)
1104 if (im->logarithmic)
1105 im->maxval = maxval * 1.1;
1107 im->maxval = maxval;
1109 /* make sure min is smaller than max */
1110 if (im->minval > im->maxval) {
1111 im->minval = 0.99 * im->maxval;
1114 /* make sure min and max are not equal */
1115 if (im->minval == im->maxval) {
1117 if (! im->logarithmic) {
1120 /* make sure min and max are not both zero */
1121 if (im->maxval == 0.0) {
1130 /* identify the point where the first gridline, label ... gets placed */
1134 time_t start, /* what is the initial time */
1135 enum tmt_en baseint, /* what is the basic interval */
1136 long basestep /* how many if these do we jump a time */
1140 localtime_r(&start, &tm);
1143 tm.tm_sec -= tm.tm_sec % basestep; break;
1146 tm.tm_min -= tm.tm_min % basestep;
1151 tm.tm_hour -= tm.tm_hour % basestep; break;
1153 /* we do NOT look at the basestep for this ... */
1156 tm.tm_hour = 0; break;
1158 /* we do NOT look at the basestep for this ... */
1162 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1163 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1170 tm.tm_mon -= tm.tm_mon % basestep; break;
1178 tm.tm_year -= (tm.tm_year+1900) % basestep;
1183 /* identify the point where the next gridline, label ... gets placed */
1186 time_t current, /* what is the initial time */
1187 enum tmt_en baseint, /* what is the basic interval */
1188 long basestep /* how many if these do we jump a time */
1193 localtime_r(¤t, &tm);
1197 tm.tm_sec += basestep; break;
1199 tm.tm_min += basestep; break;
1201 tm.tm_hour += basestep; break;
1203 tm.tm_mday += basestep; break;
1205 tm.tm_mday += 7*basestep; break;
1207 tm.tm_mon += basestep; break;
1209 tm.tm_year += basestep;
1211 madetime = mktime(&tm);
1212 } while (madetime == -1); /* this is necessary to skip impssible times
1213 like the daylight saving time skips */
1219 /* calculate values required for PRINT and GPRINT functions */
1222 print_calc(image_desc_t *im, char ***prdata)
1224 long i,ii,validsteps;
1227 int graphelement = 0;
1230 double magfact = -1;
1234 if (im->imginfo) prlines++;
1235 for(i=0;i<im->gdes_c;i++){
1236 switch(im->gdes[i].gf){
1239 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1240 rrd_set_error("realloc prdata");
1244 /* PRINT and GPRINT can now print VDEF generated values.
1245 * There's no need to do any calculations on them as these
1246 * calculations were already made.
1248 vidx = im->gdes[i].vidx;
1249 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1250 printval = im->gdes[vidx].vf.val;
1251 printtime = im->gdes[vidx].vf.when;
1252 } else { /* need to calculate max,min,avg etcetera */
1253 max_ii =((im->gdes[vidx].end
1254 - im->gdes[vidx].start)
1255 / im->gdes[vidx].step
1256 * im->gdes[vidx].ds_cnt);
1259 for( ii=im->gdes[vidx].ds;
1261 ii+=im->gdes[vidx].ds_cnt){
1262 if (! finite(im->gdes[vidx].data[ii]))
1264 if (isnan(printval)){
1265 printval = im->gdes[vidx].data[ii];
1270 switch (im->gdes[i].cf){
1273 case CF_DEVSEASONAL:
1277 printval += im->gdes[vidx].data[ii];
1280 printval = min( printval, im->gdes[vidx].data[ii]);
1284 printval = max( printval, im->gdes[vidx].data[ii]);
1287 printval = im->gdes[vidx].data[ii];
1290 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1291 if (validsteps > 1) {
1292 printval = (printval / validsteps);
1295 } /* prepare printval */
1297 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1298 char ctime_buf[128]; /* PS: for ctime_r, must be >= 26 chars */
1300 ctime_r(&printtime,ctime_buf);
1301 while(isprint(ctime_buf[iii])){iii++;}
1302 ctime_buf[iii]='\0';
1303 if (im->gdes[i].gf == GF_PRINT){
1304 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1305 sprintf((*prdata)[prlines-2],"%s (%lu)",ctime_buf,printtime);
1306 (*prdata)[prlines-1] = NULL;
1308 sprintf(im->gdes[i].legend,"%s (%lu)",ctime_buf,printtime);
1312 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1313 /* Magfact is set to -1 upon entry to print_calc. If it
1314 * is still less than 0, then we need to run auto_scale.
1315 * Otherwise, put the value into the correct units. If
1316 * the value is 0, then do not set the symbol or magnification
1317 * so next the calculation will be performed again. */
1318 if (magfact < 0.0) {
1319 auto_scale(im,&printval,&si_symb,&magfact);
1320 if (printval == 0.0)
1323 printval /= magfact;
1325 *(++percent_s) = 's';
1326 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1327 auto_scale(im,&printval,&si_symb,&magfact);
1330 if (im->gdes[i].gf == GF_PRINT){
1331 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1332 (*prdata)[prlines-1] = NULL;
1333 if (bad_format(im->gdes[i].format)) {
1334 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1337 #ifdef HAVE_SNPRINTF
1338 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1340 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1345 if (bad_format(im->gdes[i].format)) {
1346 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1349 #ifdef HAVE_SNPRINTF
1350 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1352 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1370 #ifdef WITH_PIECHART
1378 return graphelement;
1382 /* place legends with color spots */
1384 leg_place(image_desc_t *im)
1387 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1388 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1389 int fill=0, fill_last;
1391 int leg_x = border, leg_y = im->yimg;
1395 char prt_fctn; /*special printfunctions */
1398 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1399 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1400 rrd_set_error("malloc for legspace");
1404 for(i=0;i<im->gdes_c;i++){
1407 /* hid legends for rules which are not displayed */
1409 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1410 if (im->gdes[i].gf == GF_HRULE &&
1411 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1412 im->gdes[i].legend[0] = '\0';
1414 if (im->gdes[i].gf == GF_VRULE &&
1415 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1416 im->gdes[i].legend[0] = '\0';
1419 leg_cc = strlen(im->gdes[i].legend);
1421 /* is there a controle code ant the end of the legend string ? */
1422 /* and it is not a tab \\t */
1423 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1424 prt_fctn = im->gdes[i].legend[leg_cc-1];
1426 im->gdes[i].legend[leg_cc] = '\0';
1430 /* remove exess space */
1431 while (prt_fctn=='g' &&
1433 im->gdes[i].legend[leg_cc-1]==' '){
1435 im->gdes[i].legend[leg_cc]='\0';
1438 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1441 /* no interleg space if string ends in \g */
1442 fill += legspace[i];
1444 fill += gfx_get_text_width(im->canvas, fill+border,
1445 im->text_prop[TEXT_PROP_LEGEND].font,
1446 im->text_prop[TEXT_PROP_LEGEND].size,
1448 im->gdes[i].legend, 0);
1453 /* who said there was a special tag ... ?*/
1454 if (prt_fctn=='g') {
1457 if (prt_fctn == '\0') {
1458 if (i == im->gdes_c -1 ) prt_fctn ='l';
1460 /* is it time to place the legends ? */
1461 if (fill > im->ximg - 2*border){
1476 if (prt_fctn != '\0'){
1478 if (leg_c >= 2 && prt_fctn == 'j') {
1479 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1483 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1484 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1486 for(ii=mark;ii<=i;ii++){
1487 if(im->gdes[ii].legend[0]=='\0')
1488 continue; /* skip empty legends */
1489 im->gdes[ii].leg_x = leg_x;
1490 im->gdes[ii].leg_y = leg_y;
1492 gfx_get_text_width(im->canvas, leg_x,
1493 im->text_prop[TEXT_PROP_LEGEND].font,
1494 im->text_prop[TEXT_PROP_LEGEND].size,
1496 im->gdes[ii].legend, 0)
1500 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1501 if (prt_fctn == 's') leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1513 /* create a grid on the graph. it determines what to do
1514 from the values of xsize, start and end */
1516 /* the xaxis labels are determined from the number of seconds per pixel
1517 in the requested graph */
1522 calc_horizontal_grid(image_desc_t *im)
1528 int decimals, fractionals;
1530 im->ygrid_scale.labfact=2;
1532 range = im->maxval - im->minval;
1533 scaledrange = range / im->magfact;
1535 /* does the scale of this graph make it impossible to put lines
1536 on it? If so, give up. */
1537 if (isnan(scaledrange)) {
1541 /* find grid spaceing */
1543 if(isnan(im->ygridstep)){
1544 if(im->extra_flags & ALTYGRID) {
1545 /* find the value with max number of digits. Get number of digits */
1546 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1547 if(decimals <= 0) /* everything is small. make place for zero */
1550 fractionals = floor(log10(range));
1551 if(fractionals < 0) { /* small amplitude. */
1552 int len = decimals - fractionals + 1;
1553 if (im->unitslength < len) im->unitslength = len;
1554 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", len, -fractionals + 1);
1556 int len = decimals + 1;
1557 if (im->unitslength < len) im->unitslength = len;
1558 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", len);
1560 im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1561 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1562 im->ygrid_scale.gridstep = 0.1;
1563 /* should have at least 5 lines but no more then 15 */
1564 if(range/im->ygrid_scale.gridstep < 5)
1565 im->ygrid_scale.gridstep /= 10;
1566 if(range/im->ygrid_scale.gridstep > 15)
1567 im->ygrid_scale.gridstep *= 10;
1568 if(range/im->ygrid_scale.gridstep > 5) {
1569 im->ygrid_scale.labfact = 1;
1570 if(range/im->ygrid_scale.gridstep > 8)
1571 im->ygrid_scale.labfact = 2;
1574 im->ygrid_scale.gridstep /= 5;
1575 im->ygrid_scale.labfact = 5;
1579 for(i=0;ylab[i].grid > 0;i++){
1580 pixel = im->ysize / (scaledrange / ylab[i].grid);
1588 if (pixel * ylab[gridind].lfac[i] >= 2 * im->text_prop[TEXT_PROP_AXIS].size) {
1589 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1594 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1597 im->ygrid_scale.gridstep = im->ygridstep;
1598 im->ygrid_scale.labfact = im->ylabfact;
1603 int draw_horizontal_grid(image_desc_t *im)
1607 char graph_label[100];
1608 double X0=im->xorigin;
1609 double X1=im->xorigin+im->xsize;
1611 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1612 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1613 scaledstep = im->ygrid_scale.gridstep/im->magfact;
1614 for (i = sgrid; i <= egrid; i++){
1615 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1616 if ( Y0 >= im->yorigin-im->ysize
1617 && Y0 <= im->yorigin){
1618 if(i % im->ygrid_scale.labfact == 0){
1619 if (i==0 || im->symbol == ' ') {
1621 if(im->extra_flags & ALTYGRID) {
1622 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*im->viewfactor*i);
1625 sprintf(graph_label,"%4.1f",scaledstep*im->viewfactor*i);
1628 sprintf(graph_label,"%4.0f",scaledstep*im->viewfactor*i);
1632 sprintf(graph_label,"%4.1f %c",scaledstep*im->viewfactor*i, im->symbol);
1634 sprintf(graph_label,"%4.0f %c",scaledstep*im->viewfactor*i, im->symbol);
1638 gfx_new_text ( im->canvas,
1639 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1640 im->graph_col[GRC_FONT],
1641 im->text_prop[TEXT_PROP_AXIS].font,
1642 im->text_prop[TEXT_PROP_AXIS].size,
1643 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1645 gfx_new_dashed_line ( im->canvas,
1648 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1649 im->grid_dash_on, im->grid_dash_off);
1651 } else if (!(im->extra_flags & NOMINOR)) {
1652 gfx_new_dashed_line ( im->canvas,
1655 GRIDWIDTH, im->graph_col[GRC_GRID],
1656 im->grid_dash_on, im->grid_dash_off);
1664 /* logaritmic horizontal grid */
1666 horizontal_log_grid(image_desc_t *im)
1670 int minoridx=0, majoridx=0;
1671 char graph_label[100];
1673 double value, pixperstep, minstep;
1675 /* find grid spaceing */
1676 pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1678 if (isnan(pixpex)) {
1682 for(i=0;yloglab[i][0] > 0;i++){
1683 minstep = log10(yloglab[i][0]);
1684 for(ii=1;yloglab[i][ii+1] > 0;ii++){
1685 if(yloglab[i][ii+2]==0){
1686 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1690 pixperstep = pixpex * minstep;
1691 if(pixperstep > 5){minoridx = i;}
1692 if(pixperstep > 2 * im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1696 X1=im->xorigin+im->xsize;
1697 /* paint minor grid */
1698 for (value = pow((double)10, log10(im->minval)
1699 - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1700 value <= im->maxval;
1701 value *= yloglab[minoridx][0]){
1702 if (value < im->minval) continue;
1704 while(yloglab[minoridx][++i] > 0){
1705 Y0 = ytr(im,value * yloglab[minoridx][i]);
1706 if (Y0 <= im->yorigin - im->ysize) break;
1707 gfx_new_dashed_line ( im->canvas,
1710 GRIDWIDTH, im->graph_col[GRC_GRID],
1711 im->grid_dash_on, im->grid_dash_off);
1715 /* paint major grid and labels*/
1716 for (value = pow((double)10, log10(im->minval)
1717 - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1718 value <= im->maxval;
1719 value *= yloglab[majoridx][0]){
1720 if (value < im->minval) continue;
1722 while(yloglab[majoridx][++i] > 0){
1723 Y0 = ytr(im,value * yloglab[majoridx][i]);
1724 if (Y0 <= im->yorigin - im->ysize) break;
1725 gfx_new_dashed_line ( im->canvas,
1728 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1729 im->grid_dash_on, im->grid_dash_off);
1731 sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1732 gfx_new_text ( im->canvas,
1733 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1734 im->graph_col[GRC_FONT],
1735 im->text_prop[TEXT_PROP_AXIS].font,
1736 im->text_prop[TEXT_PROP_AXIS].size,
1737 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1749 int xlab_sel; /* which sort of label and grid ? */
1750 time_t ti, tilab, timajor;
1752 char graph_label[100];
1753 double X0,Y0,Y1; /* points for filled graph and more*/
1756 /* the type of time grid is determined by finding
1757 the number of seconds per pixel in the graph */
1760 if(im->xlab_user.minsec == -1){
1761 factor=(im->end - im->start)/im->xsize;
1763 while ( xlab[xlab_sel+1].minsec != -1
1764 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1765 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1766 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1767 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1768 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1769 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1770 im->xlab_user.labst = xlab[xlab_sel].labst;
1771 im->xlab_user.precis = xlab[xlab_sel].precis;
1772 im->xlab_user.stst = xlab[xlab_sel].stst;
1775 /* y coords are the same for every line ... */
1777 Y1 = im->yorigin-im->ysize;
1780 /* paint the minor grid */
1781 if (!(im->extra_flags & NOMINOR))
1783 for(ti = find_first_time(im->start,
1784 im->xlab_user.gridtm,
1785 im->xlab_user.gridst),
1786 timajor = find_first_time(im->start,
1787 im->xlab_user.mgridtm,
1788 im->xlab_user.mgridst);
1790 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1792 /* are we inside the graph ? */
1793 if (ti < im->start || ti > im->end) continue;
1794 while (timajor < ti) {
1795 timajor = find_next_time(timajor,
1796 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1798 if (ti == timajor) continue; /* skip as falls on major grid line */
1800 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1801 im->graph_col[GRC_GRID],
1802 im->grid_dash_on, im->grid_dash_off);
1807 /* paint the major grid */
1808 for(ti = find_first_time(im->start,
1809 im->xlab_user.mgridtm,
1810 im->xlab_user.mgridst);
1812 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1814 /* are we inside the graph ? */
1815 if (ti < im->start || ti > im->end) continue;
1817 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1818 im->graph_col[GRC_MGRID],
1819 im->grid_dash_on, im->grid_dash_off);
1822 /* paint the labels below the graph */
1823 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
1824 im->xlab_user.labtm,
1825 im->xlab_user.labst);
1826 ti <= im->end - im->xlab_user.precis/2;
1827 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1829 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1830 /* are we inside the graph ? */
1831 if (tilab < im->start || tilab > im->end) continue;
1834 localtime_r(&tilab, &tm);
1835 strftime(graph_label,99,im->xlab_user.stst, &tm);
1837 # error "your libc has no strftime I guess we'll abort the exercise here."
1839 gfx_new_text ( im->canvas,
1840 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size,
1841 im->graph_col[GRC_FONT],
1842 im->text_prop[TEXT_PROP_AXIS].font,
1843 im->text_prop[TEXT_PROP_AXIS].size,
1844 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1857 /* draw x and y axis */
1858 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1859 im->xorigin+im->xsize,im->yorigin-im->ysize,
1860 GRIDWIDTH, im->graph_col[GRC_AXIS]);
1862 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1863 im->xorigin+im->xsize,im->yorigin-im->ysize,
1864 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
1866 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1867 im->xorigin+im->xsize+4,im->yorigin,
1868 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
1870 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1871 im->xorigin,im->yorigin-im->ysize-4,
1872 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
1875 /* arrow for X and Y axis direction */
1876 gfx_new_area ( im->canvas,
1877 im->xorigin+im->xsize+2, im->yorigin-2,
1878 im->xorigin+im->xsize+2, im->yorigin+3,
1879 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
1880 im->graph_col[GRC_ARROW]);
1882 gfx_new_area ( im->canvas,
1883 im->xorigin-2, im->yorigin-im->ysize-2,
1884 im->xorigin+3, im->yorigin-im->ysize-2,
1885 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
1886 im->graph_col[GRC_ARROW]);
1891 grid_paint(image_desc_t *im)
1895 double X0,Y0; /* points for filled graph and more*/
1898 /* draw 3d border */
1899 node = gfx_new_area (im->canvas, 0,im->yimg,
1901 2,2,im->graph_col[GRC_SHADEA]);
1902 gfx_add_point( node , im->ximg - 2, 2 );
1903 gfx_add_point( node , im->ximg, 0 );
1904 gfx_add_point( node , 0,0 );
1905 /* gfx_add_point( node , 0,im->yimg ); */
1907 node = gfx_new_area (im->canvas, 2,im->yimg-2,
1908 im->ximg-2,im->yimg-2,
1910 im->graph_col[GRC_SHADEB]);
1911 gfx_add_point( node , im->ximg,0);
1912 gfx_add_point( node , im->ximg,im->yimg);
1913 gfx_add_point( node , 0,im->yimg);
1914 /* gfx_add_point( node , 0,im->yimg ); */
1917 if (im->draw_x_grid == 1 )
1920 if (im->draw_y_grid == 1){
1921 if(im->logarithmic){
1922 res = horizontal_log_grid(im);
1924 res = draw_horizontal_grid(im);
1927 /* dont draw horizontal grid if there is no min and max val */
1929 char *nodata = "No Data found";
1930 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1931 im->graph_col[GRC_FONT],
1932 im->text_prop[TEXT_PROP_AXIS].font,
1933 im->text_prop[TEXT_PROP_AXIS].size,
1934 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1939 /* yaxis unit description */
1940 gfx_new_text( im->canvas,
1941 12, (im->yorigin - im->ysize/2),
1942 im->graph_col[GRC_FONT],
1943 im->text_prop[TEXT_PROP_UNIT].font,
1944 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
1945 RRDGRAPH_YLEGEND_ANGLE,
1946 GFX_H_LEFT, GFX_V_CENTER,
1950 gfx_new_text( im->canvas,
1951 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
1952 im->graph_col[GRC_FONT],
1953 im->text_prop[TEXT_PROP_TITLE].font,
1954 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1955 GFX_H_CENTER, GFX_V_CENTER,
1957 /* rrdtool 'logo' */
1958 gfx_new_text( im->canvas,
1961 im->text_prop[TEXT_PROP_AXIS].font,
1962 5, im->tabwidth, 270,
1963 GFX_H_RIGHT, GFX_V_TOP,
1964 "RRDTOOL / TOBI OETIKER");
1967 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1968 for(i=0;i<im->gdes_c;i++){
1969 if(im->gdes[i].legend[0] =='\0')
1972 /* im->gdes[i].leg_y is the bottom of the legend */
1973 X0 = im->gdes[i].leg_x;
1974 Y0 = im->gdes[i].leg_y;
1975 gfx_new_text ( im->canvas, X0, Y0,
1976 im->graph_col[GRC_FONT],
1977 im->text_prop[TEXT_PROP_LEGEND].font,
1978 im->text_prop[TEXT_PROP_LEGEND].size,
1979 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1980 im->gdes[i].legend );
1981 /* The legend for GRAPH items starts with "M " to have
1982 enough space for the box */
1983 if ( im->gdes[i].gf != GF_PRINT &&
1984 im->gdes[i].gf != GF_GPRINT &&
1985 im->gdes[i].gf != GF_COMMENT) {
1988 boxH = gfx_get_text_width(im->canvas, 0,
1989 im->text_prop[TEXT_PROP_LEGEND].font,
1990 im->text_prop[TEXT_PROP_LEGEND].size,
1991 im->tabwidth,"M", 0)*1.2;
1994 /* make sure transparent colors show up all the same */
1995 node = gfx_new_area(im->canvas,
1999 im->graph_col[GRC_CANVAS]);
2000 gfx_add_point ( node, X0+boxH, Y0-boxV );
2002 node = gfx_new_area(im->canvas,
2007 gfx_add_point ( node, X0+boxH, Y0-boxV );
2008 node = gfx_new_line(im->canvas,
2010 1,im->graph_col[GRC_FONT]);
2011 gfx_add_point(node,X0+boxH,Y0);
2012 gfx_add_point(node,X0+boxH,Y0-boxV);
2013 gfx_close_path(node);
2020 /*****************************************************
2021 * lazy check make sure we rely need to create this graph
2022 *****************************************************/
2024 int lazy_check(image_desc_t *im){
2027 struct stat imgstat;
2029 if (im->lazy == 0) return 0; /* no lazy option */
2030 if (stat(im->graphfile,&imgstat) != 0)
2031 return 0; /* can't stat */
2032 /* one pixel in the existing graph is more then what we would
2034 if (time(NULL) - imgstat.st_mtime >
2035 (im->end - im->start) / im->xsize)
2037 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2038 return 0; /* the file does not exist */
2039 switch (im->canvas->imgformat) {
2041 size = PngSize(fd,&(im->ximg),&(im->yimg));
2050 #ifdef WITH_PIECHART
2052 pie_part(image_desc_t *im, gfx_color_t color,
2053 double PieCenterX, double PieCenterY, double Radius,
2054 double startangle, double endangle)
2058 double step=M_PI/50; /* Number of iterations for the circle;
2059 ** 10 is definitely too low, more than
2060 ** 50 seems to be overkill
2063 /* Strange but true: we have to work clockwise or else
2064 ** anti aliasing nor transparency don't work.
2066 ** This test is here to make sure we do it right, also
2067 ** this makes the for...next loop more easy to implement.
2068 ** The return will occur if the user enters a negative number
2069 ** (which shouldn't be done according to the specs) or if the
2070 ** programmers do something wrong (which, as we all know, never
2071 ** happens anyway :)
2073 if (endangle<startangle) return;
2075 /* Hidden feature: Radius decreases each full circle */
2077 while (angle>=2*M_PI) {
2082 node=gfx_new_area(im->canvas,
2083 PieCenterX+sin(startangle)*Radius,
2084 PieCenterY-cos(startangle)*Radius,
2087 PieCenterX+sin(endangle)*Radius,
2088 PieCenterY-cos(endangle)*Radius,
2090 for (angle=endangle;angle-startangle>=step;angle-=step) {
2092 PieCenterX+sin(angle)*Radius,
2093 PieCenterY-cos(angle)*Radius );
2100 graph_size_location(image_desc_t *im, int elements
2102 #ifdef WITH_PIECHART
2108 /* The actual size of the image to draw is determined from
2109 ** several sources. The size given on the command line is
2110 ** the graph area but we need more as we have to draw labels
2111 ** and other things outside the graph area
2114 /* +-+-------------------------------------------+
2115 ** |l|.................title.....................|
2116 ** |e+--+-------------------------------+--------+
2119 ** |l| l| main graph area | chart |
2122 ** |r+--+-------------------------------+--------+
2123 ** |e| | x-axis labels | |
2124 ** |v+--+-------------------------------+--------+
2125 ** | |..............legends......................|
2126 ** +-+-------------------------------------------+
2132 #ifdef WITH_PIECHART
2137 Xlegend =0, Ylegend =0,
2139 Xspacing =15, Yspacing =15;
2141 if (im->extra_flags & ONLY_GRAPH) {
2143 im->ximg = im->xsize;
2144 im->yimg = im->ysize;
2145 im->yorigin = im->ysize;
2149 if (im->ylegend[0] != '\0' ) {
2150 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2154 if (im->title[0] != '\0') {
2155 /* The title is placed "inbetween" two text lines so it
2156 ** automatically has some vertical spacing. The horizontal
2157 ** spacing is added here, on each side.
2159 /* don't care for the with of the title
2160 Xtitle = gfx_get_text_width(im->canvas, 0,
2161 im->text_prop[TEXT_PROP_TITLE].font,
2162 im->text_prop[TEXT_PROP_TITLE].size,
2164 im->title, 0) + 2*Xspacing; */
2165 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2171 if (im->draw_x_grid) {
2172 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2174 if (im->draw_y_grid) {
2175 Xylabel=gfx_get_text_width(im->canvas, 0,
2176 im->text_prop[TEXT_PROP_AXIS].font,
2177 im->text_prop[TEXT_PROP_AXIS].size,
2179 "0", 0) * im->unitslength + Xspacing;
2183 #ifdef WITH_PIECHART
2185 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2191 /* Now calculate the total size. Insert some spacing where
2192 desired. im->xorigin and im->yorigin need to correspond
2193 with the lower left corner of the main graph area or, if
2194 this one is not set, the imaginary box surrounding the
2197 /* The legend width cannot yet be determined, as a result we
2198 ** have problems adjusting the image to it. For now, we just
2199 ** forget about it at all; the legend will have to fit in the
2200 ** size already allocated.
2202 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2204 #ifdef WITH_PIECHART
2208 if (Xmain) im->ximg += Xspacing;
2209 #ifdef WITH_PIECHART
2210 if (Xpie) im->ximg += Xspacing;
2213 im->xorigin = Xspacing + Xylabel;
2215 /* the length of the title should not influence with width of the graph
2216 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2218 if (Xvertical) { /* unit description */
2219 im->ximg += Xvertical;
2220 im->xorigin += Xvertical;
2224 /* The vertical size is interesting... we need to compare
2225 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2226 ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2227 ** start even thinking about Ylegend.
2229 ** Do it in three portions: First calculate the inner part,
2230 ** then do the legend, then adjust the total height of the img.
2233 /* reserve space for main and/or pie */
2235 im->yimg = Ymain + Yxlabel;
2237 #ifdef WITH_PIECHART
2238 if (im->yimg < Ypie) im->yimg = Ypie;
2241 im->yorigin = im->yimg - Yxlabel;
2243 /* reserve space for the title *or* some padding above the graph */
2246 im->yorigin += Ytitle;
2248 im->yimg += 1.5*Yspacing;
2249 im->yorigin += 1.5*Yspacing;
2251 /* reserve space for padding below the graph */
2252 im->yimg += Yspacing;
2255 /* Determine where to place the legends onto the image.
2256 ** Adjust im->yimg to match the space requirements.
2258 if(leg_place(im)==-1)
2263 if (Xlegend > im->ximg) {
2265 /* reposition Pie */
2269 #ifdef WITH_PIECHART
2270 /* The pie is placed in the upper right hand corner,
2271 ** just below the title (if any) and with sufficient
2275 im->pie_x = im->ximg - Xspacing - Xpie/2;
2276 im->pie_y = im->yorigin-Ymain+Ypie/2;
2278 im->pie_x = im->ximg/2;
2279 im->pie_y = im->yorigin-Ypie/2;
2286 /* draw that picture thing ... */
2288 graph_paint(image_desc_t *im, char ***calcpr)
2291 int lazy = lazy_check(im);
2292 #ifdef WITH_PIECHART
2294 double PieStart=0.0;
2299 double areazero = 0.0;
2300 enum gf_en stack_gf = GF_PRINT;
2301 graph_desc_t *lastgdes = NULL;
2303 /* if we are lazy and there is nothing to PRINT ... quit now */
2304 if (lazy && im->prt_c==0) return 0;
2306 /* pull the data from the rrd files ... */
2308 if(data_fetch(im)==-1)
2311 /* evaluate VDEF and CDEF operations ... */
2312 if(data_calc(im)==-1)
2315 #ifdef WITH_PIECHART
2316 /* check if we need to draw a piechart */
2317 for(i=0;i<im->gdes_c;i++){
2318 if (im->gdes[i].gf == GF_PART) {
2325 /* calculate and PRINT and GPRINT definitions. We have to do it at
2326 * this point because it will affect the length of the legends
2327 * if there are no graph elements we stop here ...
2328 * if we are lazy, try to quit ...
2330 i=print_calc(im,calcpr);
2333 #ifdef WITH_PIECHART
2336 ) || lazy) return 0;
2338 #ifdef WITH_PIECHART
2339 /* If there's only the pie chart to draw, signal this */
2340 if (i==0) piechart=2;
2343 /* get actual drawing data and find min and max values*/
2344 if(data_proc(im)==-1)
2347 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2349 if(!im->rigid && ! im->logarithmic)
2350 expand_range(im); /* make sure the upper and lower limit are
2353 if (!calc_horizontal_grid(im))
2360 /**************************************************************
2361 *** Calculating sizes and locations became a bit confusing ***
2362 *** so I moved this into a separate function. ***
2363 **************************************************************/
2364 if(graph_size_location(im,i
2365 #ifdef WITH_PIECHART
2371 /* the actual graph is created by going through the individual
2372 graph elements and then drawing them */
2374 node=gfx_new_area ( im->canvas,
2378 im->graph_col[GRC_BACK]);
2380 gfx_add_point(node,0, im->yimg);
2382 #ifdef WITH_PIECHART
2383 if (piechart != 2) {
2385 node=gfx_new_area ( im->canvas,
2386 im->xorigin, im->yorigin,
2387 im->xorigin + im->xsize, im->yorigin,
2388 im->xorigin + im->xsize, im->yorigin-im->ysize,
2389 im->graph_col[GRC_CANVAS]);
2391 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2393 if (im->minval > 0.0)
2394 areazero = im->minval;
2395 if (im->maxval < 0.0)
2396 areazero = im->maxval;
2397 #ifdef WITH_PIECHART
2401 #ifdef WITH_PIECHART
2403 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2407 for(i=0;i<im->gdes_c;i++){
2408 switch(im->gdes[i].gf){
2421 for (ii = 0; ii < im->xsize; ii++)
2423 if (!isnan(im->gdes[i].p_data[ii]) &&
2424 im->gdes[i].p_data[ii] > 0.0)
2426 /* generate a tick */
2427 gfx_new_line(im->canvas, im -> xorigin + ii,
2428 im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2432 im -> gdes[i].col );
2438 stack_gf = im->gdes[i].gf;
2440 /* fix data points at oo and -oo */
2441 for(ii=0;ii<im->xsize;ii++){
2442 if (isinf(im->gdes[i].p_data[ii])){
2443 if (im->gdes[i].p_data[ii] > 0) {
2444 im->gdes[i].p_data[ii] = im->maxval ;
2446 im->gdes[i].p_data[ii] = im->minval ;
2452 /* *******************************************************
2457 -------|--t-1--t--------------------------------
2459 if we know the value at time t was a then
2460 we draw a square from t-1 to t with the value a.
2462 ********************************************************* */
2463 if (im->gdes[i].col != 0x0){
2464 /* GF_LINE and friend */
2465 if(stack_gf == GF_LINE ){
2467 for(ii=1;ii<im->xsize;ii++){
2468 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2472 if ( node == NULL ) {
2473 if ( im->slopemode == 0 ){
2474 node = gfx_new_line(im->canvas,
2475 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2476 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2477 im->gdes[i].linewidth,
2480 node = gfx_new_line(im->canvas,
2481 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2482 ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2483 im->gdes[i].linewidth,
2487 if ( im->slopemode==0 ){
2488 gfx_add_point(node,ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2490 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2495 float ybase0 = DNAN,ytop0=DNAN;
2496 for(ii=0;ii<im->xsize;ii++){
2497 /* keep things simple for now, just draw these bars
2498 do not try to build a big and complex area */
2500 if ( im->slopemode == 0 && ii==0){
2503 if ( isnan(im->gdes[i].p_data[ii]) ) {
2507 ytop = ytr(im,im->gdes[i].p_data[ii]);
2508 if ( lastgdes && im->gdes[i].stack ) {
2509 ybase = ytr(im,lastgdes->p_data[ii]);
2511 ybase = ytr(im,areazero);
2513 if ( ybase == ytop ){
2517 /* every area has to be wound clock-wise,
2518 so we have to make sur base remains base */
2524 if ( im->slopemode == 0){
2528 if ( !isnan(ybase0) ){
2529 node = gfx_new_area(im->canvas,
2530 ii-1+im->xorigin,ybase0,
2531 ii-1+im->xorigin,ytop0,
2532 ii+im->xorigin,ytop,
2536 ii+im->xorigin,ybase
2542 } /* else GF_LINE */
2543 } /* if color != 0x0 */
2544 /* make sure we do not run into trouble when stacking on NaN */
2545 for(ii=0;ii<im->xsize;ii++){
2546 if (isnan(im->gdes[i].p_data[ii])) {
2547 if (lastgdes && (im->gdes[i].stack)) {
2548 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2550 im->gdes[i].p_data[ii] = ytr(im,areazero);
2554 lastgdes = &(im->gdes[i]);
2556 #ifdef WITH_PIECHART
2558 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2559 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2561 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2562 pie_part(im,im->gdes[i].col,
2563 im->pie_x,im->pie_y,im->piesize*0.4,
2564 M_PI*2.0*PieStart/100.0,
2565 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2566 PieStart += im->gdes[i].yrule;
2573 #ifdef WITH_PIECHART
2581 /* grid_paint also does the text */
2582 if( !(im->extra_flags & ONLY_GRAPH) )
2586 if( !(im->extra_flags & ONLY_GRAPH) )
2589 /* the RULES are the last thing to paint ... */
2590 for(i=0;i<im->gdes_c;i++){
2592 switch(im->gdes[i].gf){
2594 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2595 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2597 if(im->gdes[i].yrule >= im->minval
2598 && im->gdes[i].yrule <= im->maxval)
2599 gfx_new_line(im->canvas,
2600 im->xorigin,ytr(im,im->gdes[i].yrule),
2601 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2602 1.0,im->gdes[i].col);
2605 if(im->gdes[i].xrule == 0) { /* fetch variable */
2606 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2608 if(im->gdes[i].xrule >= im->start
2609 && im->gdes[i].xrule <= im->end)
2610 gfx_new_line(im->canvas,
2611 xtr(im,im->gdes[i].xrule),im->yorigin,
2612 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2613 1.0,im->gdes[i].col);
2621 if (strcmp(im->graphfile,"-")==0) {
2622 fo = im->graphhandle ? im->graphhandle : stdout;
2623 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2624 /* Change translation mode for stdout to BINARY */
2625 _setmode( _fileno( fo ), O_BINARY );
2628 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2629 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2630 rrd_strerror(errno));
2634 gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2635 if (strcmp(im->graphfile,"-") != 0)
2641 /*****************************************************
2643 *****************************************************/
2646 gdes_alloc(image_desc_t *im){
2649 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2650 * sizeof(graph_desc_t)))==NULL){
2651 rrd_set_error("realloc graph_descs");
2656 im->gdes[im->gdes_c-1].step=im->step;
2657 im->gdes[im->gdes_c-1].stack=0;
2658 im->gdes[im->gdes_c-1].debug=0;
2659 im->gdes[im->gdes_c-1].start=im->start;
2660 im->gdes[im->gdes_c-1].end=im->end;
2661 im->gdes[im->gdes_c-1].vname[0]='\0';
2662 im->gdes[im->gdes_c-1].data=NULL;
2663 im->gdes[im->gdes_c-1].ds_namv=NULL;
2664 im->gdes[im->gdes_c-1].data_first=0;
2665 im->gdes[im->gdes_c-1].p_data=NULL;
2666 im->gdes[im->gdes_c-1].rpnp=NULL;
2667 im->gdes[im->gdes_c-1].shift=0;
2668 im->gdes[im->gdes_c-1].col = 0x0;
2669 im->gdes[im->gdes_c-1].legend[0]='\0';
2670 im->gdes[im->gdes_c-1].format[0]='\0';
2671 im->gdes[im->gdes_c-1].rrd[0]='\0';
2672 im->gdes[im->gdes_c-1].ds=-1;
2673 im->gdes[im->gdes_c-1].p_data=NULL;
2674 im->gdes[im->gdes_c-1].yrule=DNAN;
2675 im->gdes[im->gdes_c-1].xrule=0;
2679 /* copies input untill the first unescaped colon is found
2680 or until input ends. backslashes have to be escaped as well */
2682 scan_for_col(char *input, int len, char *output)
2687 input[inp] != ':' &&
2690 if (input[inp] == '\\' &&
2691 input[inp+1] != '\0' &&
2692 (input[inp+1] == '\\' ||
2693 input[inp+1] == ':')){
2694 output[outp++] = input[++inp];
2697 output[outp++] = input[inp];
2700 output[outp] = '\0';
2703 /* Some surgery done on this function, it became ridiculously big.
2705 ** - initializing now in rrd_graph_init()
2706 ** - options parsing now in rrd_graph_options()
2707 ** - script parsing now in rrd_graph_script()
2710 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
2713 rrd_graph_init(&im);
2714 im.graphhandle = stream;
2716 rrd_graph_options(argc,argv,&im);
2717 if (rrd_test_error()) {
2722 if (strlen(argv[optind])>=MAXPATH) {
2723 rrd_set_error("filename (including path) too long");
2727 strncpy(im.graphfile,argv[optind],MAXPATH-1);
2728 im.graphfile[MAXPATH-1]='\0';
2730 rrd_graph_script(argc,argv,&im,1);
2731 if (rrd_test_error()) {
2736 /* Everything is now read and the actual work can start */
2739 if (graph_paint(&im,prdata)==-1){
2744 /* The image is generated and needs to be output.
2745 ** Also, if needed, print a line with information about the image.
2755 /* maybe prdata is not allocated yet ... lets do it now */
2756 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2757 rrd_set_error("malloc imginfo");
2761 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2763 rrd_set_error("malloc imginfo");
2766 filename=im.graphfile+strlen(im.graphfile);
2767 while(filename > im.graphfile) {
2768 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2772 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2779 rrd_graph_init(image_desc_t *im)
2786 #ifdef HAVE_SETLOCALE
2787 setlocale(LC_TIME,"");
2792 im->xlab_user.minsec = -1;
2798 im->ylegend[0] = '\0';
2799 im->title[0] = '\0';
2802 im->unitsexponent= 9999;
2805 im->viewfactor = 1.0;
2812 im->logarithmic = 0;
2813 im->ygridstep = DNAN;
2814 im->draw_x_grid = 1;
2815 im->draw_y_grid = 1;
2820 im->canvas = gfx_new_canvas();
2821 im->grid_dash_on = 1;
2822 im->grid_dash_off = 1;
2823 im->tabwidth = 40.0;
2825 for(i=0;i<DIM(graph_col);i++)
2826 im->graph_col[i]=graph_col[i];
2828 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2831 char rrd_win_default_font[1000];
2832 windir = getenv("windir");
2833 /* %windir% is something like D:\windows or C:\winnt */
2834 if (windir != NULL) {
2835 strncpy(rrd_win_default_font,windir,999);
2836 rrd_win_default_font[999] = '\0';
2837 strcat(rrd_win_default_font,"\\fonts\\");
2838 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
2839 for(i=0;i<DIM(text_prop);i++){
2840 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
2841 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
2848 deffont = getenv("RRD_DEFAULT_FONT");
2849 if (deffont != NULL) {
2850 for(i=0;i<DIM(text_prop);i++){
2851 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
2852 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
2856 for(i=0;i<DIM(text_prop);i++){
2857 im->text_prop[i].size = text_prop[i].size;
2858 strcpy(im->text_prop[i].font,text_prop[i].font);
2863 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2866 char *parsetime_error = NULL;
2867 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2868 time_t start_tmp=0,end_tmp=0;
2870 struct rrd_time_value start_tv, end_tv;
2872 optind = 0; opterr = 0; /* initialize getopt */
2874 parsetime("end-24h", &start_tv);
2875 parsetime("now", &end_tv);
2878 static struct option long_options[] =
2880 {"start", required_argument, 0, 's'},
2881 {"end", required_argument, 0, 'e'},
2882 {"x-grid", required_argument, 0, 'x'},
2883 {"y-grid", required_argument, 0, 'y'},
2884 {"vertical-label",required_argument,0,'v'},
2885 {"width", required_argument, 0, 'w'},
2886 {"height", required_argument, 0, 'h'},
2887 {"interlaced", no_argument, 0, 'i'},
2888 {"upper-limit",required_argument, 0, 'u'},
2889 {"lower-limit",required_argument, 0, 'l'},
2890 {"rigid", no_argument, 0, 'r'},
2891 {"base", required_argument, 0, 'b'},
2892 {"logarithmic",no_argument, 0, 'o'},
2893 {"color", required_argument, 0, 'c'},
2894 {"font", required_argument, 0, 'n'},
2895 {"title", required_argument, 0, 't'},
2896 {"imginfo", required_argument, 0, 'f'},
2897 {"imgformat", required_argument, 0, 'a'},
2898 {"lazy", no_argument, 0, 'z'},
2899 {"zoom", required_argument, 0, 'm'},
2900 {"no-legend", no_argument, 0, 'g'},
2901 {"force-rules-legend",no_argument,0, 'F'},
2902 {"only-graph", no_argument, 0, 'j'},
2903 {"alt-y-grid", no_argument, 0, 'Y'},
2904 {"no-minor", no_argument, 0, 'I'},
2905 {"slope-mode", no_argument, 0, 'E'},
2906 {"alt-autoscale", no_argument, 0, 'A'},
2907 {"alt-autoscale-max", no_argument, 0, 'M'},
2908 {"no-gridfit", no_argument, 0, 'N'},
2909 {"units-exponent",required_argument, 0, 'X'},
2910 {"units-length",required_argument, 0, 'L'},
2911 {"step", required_argument, 0, 'S'},
2912 {"tabwidth", required_argument, 0, 'T'},
2913 {"font-render-mode", required_argument, 0, 'R'},
2914 {"font-smoothing-threshold", required_argument, 0, 'B'},
2916 int option_index = 0;
2918 int col_start,col_end;
2920 opt = getopt_long(argc, argv,
2921 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:",
2922 long_options, &option_index);
2929 im->extra_flags |= NOMINOR;
2932 im->extra_flags |= ALTYGRID;
2935 im->extra_flags |= ALTAUTOSCALE;
2938 im->extra_flags |= ALTAUTOSCALE_MAX;
2941 im->extra_flags |= ONLY_GRAPH;
2944 im->extra_flags |= NOLEGEND;
2947 im->extra_flags |= FORCE_RULES_LEGEND;
2950 im->unitsexponent = atoi(optarg);
2953 im->unitslength = atoi(optarg);
2956 im->tabwidth = atof(optarg);
2959 im->step = atoi(optarg);
2965 if ((parsetime_error = parsetime(optarg, &start_tv))) {
2966 rrd_set_error( "start time: %s", parsetime_error );
2971 if ((parsetime_error = parsetime(optarg, &end_tv))) {
2972 rrd_set_error( "end time: %s", parsetime_error );
2977 if(strcmp(optarg,"none") == 0){
2983 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2985 &im->xlab_user.gridst,
2987 &im->xlab_user.mgridst,
2989 &im->xlab_user.labst,
2990 &im->xlab_user.precis,
2991 &stroff) == 7 && stroff != 0){
2992 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2993 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
2994 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2995 rrd_set_error("unknown keyword %s",scan_gtm);
2997 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2998 rrd_set_error("unknown keyword %s",scan_mtm);
3000 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3001 rrd_set_error("unknown keyword %s",scan_ltm);
3004 im->xlab_user.minsec = 1;
3005 im->xlab_user.stst = im->xlab_form;
3007 rrd_set_error("invalid x-grid format");
3013 if(strcmp(optarg,"none") == 0){
3021 &im->ylabfact) == 2) {
3022 if(im->ygridstep<=0){
3023 rrd_set_error("grid step must be > 0");
3025 } else if (im->ylabfact < 1){
3026 rrd_set_error("label factor must be > 0");
3030 rrd_set_error("invalid y-grid format");
3035 strncpy(im->ylegend,optarg,150);
3036 im->ylegend[150]='\0';
3039 im->maxval = atof(optarg);
3042 im->minval = atof(optarg);
3045 im->base = atol(optarg);
3046 if(im->base != 1024 && im->base != 1000 ){
3047 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3052 long_tmp = atol(optarg);
3053 if (long_tmp < 10) {
3054 rrd_set_error("width below 10 pixels");
3057 im->xsize = long_tmp;
3060 long_tmp = atol(optarg);
3061 if (long_tmp < 10) {
3062 rrd_set_error("height below 10 pixels");
3065 im->ysize = long_tmp;
3068 im->canvas->interlaced = 1;
3074 im->imginfo = optarg;
3077 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3078 rrd_set_error("unsupported graphics format '%s'",optarg);
3090 im->logarithmic = 1;
3091 if (isnan(im->minval))
3096 "%10[A-Z]#%n%8lx%n",
3097 col_nam,&col_start,&color,&col_end) == 2){
3099 int col_len = col_end - col_start;
3102 color = (color << 8) + 0xff /* shift left by 8 */;
3107 rrd_set_error("the color format is #RRGGBB[AA]");
3110 if((ci=grc_conv(col_nam)) != -1){
3111 im->graph_col[ci]=color;
3113 rrd_set_error("invalid color name '%s'",col_nam);
3117 rrd_set_error("invalid color def format");
3127 "%10[A-Z]:%lf:%1000s",
3128 prop,&size,font) == 3){
3130 if((sindex=text_prop_conv(prop)) != -1){
3131 im->text_prop[sindex].size=size;
3132 strcpy(im->text_prop[sindex].font,font);
3133 if (sindex==0) { /* the default */
3134 im->text_prop[TEXT_PROP_TITLE].size=size;
3135 strcpy(im->text_prop[TEXT_PROP_TITLE].font,font);
3136 im->text_prop[TEXT_PROP_AXIS].size=size;
3137 strcpy(im->text_prop[TEXT_PROP_AXIS].font,font);
3138 im->text_prop[TEXT_PROP_UNIT].size=size;
3139 strcpy(im->text_prop[TEXT_PROP_UNIT].font,font);
3140 im->text_prop[TEXT_PROP_LEGEND].size=size;
3141 strcpy(im->text_prop[TEXT_PROP_LEGEND].font,font);
3144 rrd_set_error("invalid fonttag '%s'",prop);
3148 rrd_set_error("invalid text property format");
3154 im->canvas->zoom = atof(optarg);
3155 if (im->canvas->zoom <= 0.0) {
3156 rrd_set_error("zoom factor must be > 0");
3161 strncpy(im->title,optarg,150);
3162 im->title[150]='\0';
3166 if ( strcmp( optarg, "normal" ) == 0 )
3167 im->canvas->aa_type = AA_NORMAL;
3168 else if ( strcmp( optarg, "light" ) == 0 )
3169 im->canvas->aa_type = AA_LIGHT;
3170 else if ( strcmp( optarg, "mono" ) == 0 )
3171 im->canvas->aa_type = AA_NONE;
3174 rrd_set_error("unknown font-render-mode '%s'", optarg );
3180 im->canvas->font_aa_threshold = atof(optarg);
3185 rrd_set_error("unknown option '%c'", optopt);
3187 rrd_set_error("unknown option '%s'",argv[optind-1]);
3192 if (optind >= argc) {
3193 rrd_set_error("missing filename");
3197 if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
3198 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3202 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3203 /* error string is set in parsetime.c */
3207 if (start_tmp < 3600*24*365*10){
3208 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3212 if (end_tmp < start_tmp) {
3213 rrd_set_error("start (%ld) should be less than end (%ld)",
3214 start_tmp, end_tmp);
3218 im->start = start_tmp;
3220 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3224 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3226 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3227 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3233 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3236 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3238 color=strstr(var,"#");
3241 rrd_set_error("Found no color in %s",err);
3250 rest=strstr(color,":");
3258 sscanf(color,"#%6lx%n",&col,&n);
3259 col = (col << 8) + 0xff /* shift left by 8 */;
3260 if (n!=7) rrd_set_error("Color problem in %s",err);
3263 sscanf(color,"#%8lx%n",&col,&n);
3266 rrd_set_error("Color problem in %s",err);
3268 if (rrd_test_error()) return 0;
3275 int bad_format(char *fmt) {
3279 while (*ptr != '\0')
3280 if (*ptr++ == '%') {
3282 /* line cannot end with percent char */
3283 if (*ptr == '\0') return 1;
3285 /* '%s', '%S' and '%%' are allowed */
3286 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3288 /* or else '% 6.2lf' and such are allowed */
3291 /* optional padding character */
3292 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3294 /* This should take care of 'm.n' with all three optional */
3295 while (*ptr >= '0' && *ptr <= '9') ptr++;
3296 if (*ptr == '.') ptr++;
3297 while (*ptr >= '0' && *ptr <= '9') ptr++;
3299 /* Either 'le', 'lf' or 'lg' must follow here */
3300 if (*ptr++ != 'l') return 1;
3301 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3312 vdef_parse(gdes,str)
3313 struct graph_desc_t *gdes;
3316 /* A VDEF currently is either "func" or "param,func"
3317 * so the parsing is rather simple. Change if needed.
3324 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3325 if (n== (int)strlen(str)) { /* matched */
3329 sscanf(str,"%29[A-Z]%n",func,&n);
3330 if (n== (int)strlen(str)) { /* matched */
3333 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3340 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3341 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3342 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3343 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3344 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3345 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3346 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3348 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3355 switch (gdes->vf.op) {
3357 if (isnan(param)) { /* no parameter given */
3358 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3364 if (param>=0.0 && param<=100.0) {
3365 gdes->vf.param = param;
3366 gdes->vf.val = DNAN; /* undefined */
3367 gdes->vf.when = 0; /* undefined */
3369 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3383 gdes->vf.param = DNAN;
3384 gdes->vf.val = DNAN;
3387 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3404 graph_desc_t *src,*dst;
3408 dst = &im->gdes[gdi];
3409 src = &im->gdes[dst->vidx];
3410 data = src->data + src->ds;
3411 steps = (src->end - src->start) / src->step;
3414 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3421 switch (dst->vf.op) {
3422 case VDEF_PERCENT: {
3423 rrd_value_t * array;
3427 if ((array = malloc(steps*sizeof(double)))==NULL) {
3428 rrd_set_error("malloc VDEV_PERCENT");
3431 for (step=0;step < steps; step++) {
3432 array[step]=data[step*src->ds_cnt];
3434 qsort(array,step,sizeof(double),vdef_percent_compar);
3436 field = (steps-1)*dst->vf.param/100;
3437 dst->vf.val = array[field];
3438 dst->vf.when = 0; /* no time component */
3441 for(step=0;step<steps;step++)
3442 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3448 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3449 if (step == steps) {
3453 dst->vf.val = data[step*src->ds_cnt];
3454 dst->vf.when = src->start + (step+1)*src->step;
3456 while (step != steps) {
3457 if (finite(data[step*src->ds_cnt])) {
3458 if (data[step*src->ds_cnt] > dst->vf.val) {
3459 dst->vf.val = data[step*src->ds_cnt];
3460 dst->vf.when = src->start + (step+1)*src->step;
3467 case VDEF_AVERAGE: {
3470 for (step=0;step<steps;step++) {
3471 if (finite(data[step*src->ds_cnt])) {
3472 sum += data[step*src->ds_cnt];
3477 if (dst->vf.op == VDEF_TOTAL) {
3478 dst->vf.val = sum*src->step;
3479 dst->vf.when = cnt*src->step; /* not really "when" */
3481 dst->vf.val = sum/cnt;
3482 dst->vf.when = 0; /* no time component */
3492 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3493 if (step == steps) {
3497 dst->vf.val = data[step*src->ds_cnt];
3498 dst->vf.when = src->start + (step+1)*src->step;
3500 while (step != steps) {
3501 if (finite(data[step*src->ds_cnt])) {
3502 if (data[step*src->ds_cnt] < dst->vf.val) {
3503 dst->vf.val = data[step*src->ds_cnt];
3504 dst->vf.when = src->start + (step+1)*src->step;
3511 /* The time value returned here is one step before the
3512 * actual time value. This is the start of the first
3516 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3517 if (step == steps) { /* all entries were NaN */
3521 dst->vf.val = data[step*src->ds_cnt];
3522 dst->vf.when = src->start + step*src->step;
3526 /* The time value returned here is the
3527 * actual time value. This is the end of the last
3531 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3532 if (step < 0) { /* all entries were NaN */
3536 dst->vf.val = data[step*src->ds_cnt];
3537 dst->vf.when = src->start + (step+1)*src->step;
3544 /* NaN < -INF < finite_values < INF */
3546 vdef_percent_compar(a,b)
3549 /* Equality is not returned; this doesn't hurt except
3550 * (maybe) for a little performance.
3553 /* First catch NaN values. They are smallest */
3554 if (isnan( *(double *)a )) return -1;
3555 if (isnan( *(double *)b )) return 1;
3557 /* NaN doesn't reach this part so INF and -INF are extremes.
3558 * The sign from isinf() is compatible with the sign we return
3560 if (isinf( *(double *)a )) return isinf( *(double *)a );
3561 if (isinf( *(double *)b )) return isinf( *(double *)b );
3563 /* If we reach this, both values must be finite */
3564 if ( *(double *)a < *(double *)b ) return -1; else return 1;