1 /****************************************************************************
2 * RRDtool 1.2.15 Copyright by Tobi Oetiker, 1997-2006
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
16 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
29 #include "rrd_graph.h"
31 /* some constant definitions */
35 #ifndef RRD_DEFAULT_FONT
36 /* there is special code later to pick Cour.ttf when running on windows */
37 #define RRD_DEFAULT_FONT "DejaVuSansMono-Roman.ttf"
40 text_prop_t text_prop[] = {
41 { 8.0, RRD_DEFAULT_FONT }, /* default */
42 { 9.0, RRD_DEFAULT_FONT }, /* title */
43 { 7.0, RRD_DEFAULT_FONT }, /* axis */
44 { 8.0, RRD_DEFAULT_FONT }, /* unit */
45 { 8.0, RRD_DEFAULT_FONT } /* legend */
49 {0, 0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
50 {2, 0, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
51 {5, 0, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
52 {10, 0, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
53 {30, 0, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
54 {60, 0, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
55 {60, 24*3600, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,4, 0,"%a %H:%M"},
56 {180, 0, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
57 {180, 24*3600, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,12, 0,"%a %H:%M"},
58 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
59 {600, 0, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
60 {1200, 0, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%d"},
61 {1800, 0, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a %d"},
62 {2400, 0, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
63 {3600, 0, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
64 {3*3600, 0, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
65 {6*3600, 0, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
66 {48*3600, 0, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
67 {315360, 0, TMT_MONTH,3, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%Y"},
68 {10*24*3600, 0, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
69 {-1,0,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
72 /* sensible y label intervals ...*/
90 gfx_color_t graph_col[] = /* default colors */
91 { 0xFFFFFFFF, /* canvas */
92 0xF0F0F0FF, /* background */
93 0xD0D0D0FF, /* shade A */
94 0xA0A0A0FF, /* shade B */
95 0x90909080, /* grid */
96 0xE0505080, /* major grid */
97 0x000000FF, /* font */
98 0x802020FF, /* arrow */
99 0x202020FF, /* axis */
100 0x000000FF /* frame */
107 # define DPRINT(x) (void)(printf x, printf("\n"))
113 /* initialize with xtr(im,0); */
115 xtr(image_desc_t *im,time_t mytime){
118 pixie = (double) im->xsize / (double)(im->end - im->start);
121 return (int)((double)im->xorigin
122 + pixie * ( mytime - im->start ) );
125 /* translate data values into y coordinates */
127 ytr(image_desc_t *im, double value){
132 pixie = (double) im->ysize / (im->maxval - im->minval);
134 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
136 } else if(!im->logarithmic) {
137 yval = im->yorigin - pixie * (value - im->minval);
139 if (value < im->minval) {
142 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
145 /* make sure we don't return anything too unreasonable. GD lib can
146 get terribly slow when drawing lines outside its scope. This is
147 especially problematic in connection with the rigid option */
149 /* keep yval as-is */
150 } else if (yval > im->yorigin) {
151 yval = im->yorigin +0.00001;
152 } else if (yval < im->yorigin - im->ysize){
153 yval = im->yorigin - im->ysize - 0.00001;
160 /* conversion function for symbolic entry names */
163 #define conv_if(VV,VVV) \
164 if (strcmp(#VV, string) == 0) return VVV ;
166 enum gf_en gf_conv(char *string){
168 conv_if(PRINT,GF_PRINT)
169 conv_if(GPRINT,GF_GPRINT)
170 conv_if(COMMENT,GF_COMMENT)
171 conv_if(HRULE,GF_HRULE)
172 conv_if(VRULE,GF_VRULE)
173 conv_if(LINE,GF_LINE)
174 conv_if(AREA,GF_AREA)
175 conv_if(STACK,GF_STACK)
176 conv_if(TICK,GF_TICK)
178 conv_if(CDEF,GF_CDEF)
179 conv_if(VDEF,GF_VDEF)
181 conv_if(PART,GF_PART)
183 conv_if(XPORT,GF_XPORT)
184 conv_if(SHIFT,GF_SHIFT)
189 enum gfx_if_en if_conv(char *string){
199 enum tmt_en tmt_conv(char *string){
201 conv_if(SECOND,TMT_SECOND)
202 conv_if(MINUTE,TMT_MINUTE)
203 conv_if(HOUR,TMT_HOUR)
205 conv_if(WEEK,TMT_WEEK)
206 conv_if(MONTH,TMT_MONTH)
207 conv_if(YEAR,TMT_YEAR)
211 enum grc_en grc_conv(char *string){
213 conv_if(BACK,GRC_BACK)
214 conv_if(CANVAS,GRC_CANVAS)
215 conv_if(SHADEA,GRC_SHADEA)
216 conv_if(SHADEB,GRC_SHADEB)
217 conv_if(GRID,GRC_GRID)
218 conv_if(MGRID,GRC_MGRID)
219 conv_if(FONT,GRC_FONT)
220 conv_if(ARROW,GRC_ARROW)
221 conv_if(AXIS,GRC_AXIS)
222 conv_if(FRAME,GRC_FRAME)
227 enum text_prop_en text_prop_conv(char *string){
229 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
230 conv_if(TITLE,TEXT_PROP_TITLE)
231 conv_if(AXIS,TEXT_PROP_AXIS)
232 conv_if(UNIT,TEXT_PROP_UNIT)
233 conv_if(LEGEND,TEXT_PROP_LEGEND)
241 im_free(image_desc_t *im)
245 if (im == NULL) return 0;
246 for(i=0;i<(unsigned)im->gdes_c;i++){
247 if (im->gdes[i].data_first){
248 /* careful here, because a single pointer can occur several times */
249 free (im->gdes[i].data);
250 if (im->gdes[i].ds_namv){
251 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
252 free(im->gdes[i].ds_namv[ii]);
253 free(im->gdes[i].ds_namv);
256 free (im->gdes[i].p_data);
257 free (im->gdes[i].rpnp);
260 gfx_destroy(im->canvas);
264 /* find SI magnitude symbol for the given number*/
267 image_desc_t *im, /* image description */
274 char *symbol[] = {"a", /* 10e-18 Atto */
275 "f", /* 10e-15 Femto */
276 "p", /* 10e-12 Pico */
277 "n", /* 10e-9 Nano */
278 "u", /* 10e-6 Micro */
279 "m", /* 10e-3 Milli */
284 "T", /* 10e12 Tera */
285 "P", /* 10e15 Peta */
291 if (*value == 0.0 || isnan(*value) ) {
295 sindex = floor(log(fabs(*value))/log((double)im->base));
296 *magfact = pow((double)im->base, (double)sindex);
297 (*value) /= (*magfact);
299 if ( sindex <= symbcenter && sindex >= -symbcenter) {
300 (*symb_ptr) = symbol[sindex+symbcenter];
308 static char si_symbol[] = {
309 'a', /* 10e-18 Atto */
310 'f', /* 10e-15 Femto */
311 'p', /* 10e-12 Pico */
312 'n', /* 10e-9 Nano */
313 'u', /* 10e-6 Micro */
314 'm', /* 10e-3 Milli */
319 'T', /* 10e12 Tera */
320 'P', /* 10e15 Peta */
323 static const int si_symbcenter = 6;
325 /* find SI magnitude symbol for the numbers on the y-axis*/
328 image_desc_t *im /* image description */
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 if ( ((viewdigits+si_symbcenter) < sizeof(si_symbol)) &&
352 ((viewdigits+si_symbcenter) >= 0) )
353 im->symbol = si_symbol[(int)viewdigits+si_symbcenter];
358 /* move min and max values around to become sensible */
361 expand_range(image_desc_t *im)
363 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
364 600.0,500.0,400.0,300.0,250.0,
365 200.0,125.0,100.0,90.0,80.0,
366 75.0,70.0,60.0,50.0,40.0,30.0,
367 25.0,20.0,10.0,9.0,8.0,
368 7.0,6.0,5.0,4.0,3.5,3.0,
369 2.5,2.0,1.8,1.5,1.2,1.0,
370 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
372 double scaled_min,scaled_max;
379 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
380 im->minval,im->maxval,im->magfact);
383 if (isnan(im->ygridstep)){
384 if(im->extra_flags & ALTAUTOSCALE) {
385 /* measure the amplitude of the function. Make sure that
386 graph boundaries are slightly higher then max/min vals
387 so we can see amplitude on the graph */
390 delt = im->maxval - im->minval;
392 fact = 2.0 * pow(10.0,
393 floor(log10(max(fabs(im->minval), fabs(im->maxval))/im->magfact)) - 2);
395 adj = (fact - delt) * 0.55;
397 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
403 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
404 /* measure the amplitude of the function. Make sure that
405 graph boundaries are slightly higher than max vals
406 so we can see amplitude on the graph */
407 adj = (im->maxval - im->minval) * 0.1;
411 scaled_min = im->minval / im->magfact;
412 scaled_max = im->maxval / im->magfact;
414 for (i=1; sensiblevalues[i] > 0; i++){
415 if (sensiblevalues[i-1]>=scaled_min &&
416 sensiblevalues[i]<=scaled_min)
417 im->minval = sensiblevalues[i]*(im->magfact);
419 if (-sensiblevalues[i-1]<=scaled_min &&
420 -sensiblevalues[i]>=scaled_min)
421 im->minval = -sensiblevalues[i-1]*(im->magfact);
423 if (sensiblevalues[i-1] >= scaled_max &&
424 sensiblevalues[i] <= scaled_max)
425 im->maxval = sensiblevalues[i-1]*(im->magfact);
427 if (-sensiblevalues[i-1]<=scaled_max &&
428 -sensiblevalues[i] >=scaled_max)
429 im->maxval = -sensiblevalues[i]*(im->magfact);
433 /* adjust min and max to the grid definition if there is one */
434 im->minval = (double)im->ylabfact * im->ygridstep *
435 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
436 im->maxval = (double)im->ylabfact * im->ygridstep *
437 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
441 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
442 im->minval,im->maxval,im->magfact);
447 apply_gridfit(image_desc_t *im)
449 if (isnan(im->minval) || isnan(im->maxval))
452 if (im->logarithmic) {
453 double ya, yb, ypix, ypixfrac;
454 double log10_range = log10(im->maxval) - log10(im->minval);
455 ya = pow((double)10, floor(log10(im->minval)));
456 while (ya < im->minval)
459 return; /* don't have y=10^x gridline */
461 if (yb <= im->maxval) {
462 /* we have at least 2 y=10^x gridlines.
463 Make sure distance between them in pixels
464 are an integer by expanding im->maxval */
465 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
466 double factor = y_pixel_delta / floor(y_pixel_delta);
467 double new_log10_range = factor * log10_range;
468 double new_ymax_log10 = log10(im->minval) + new_log10_range;
469 im->maxval = pow(10, new_ymax_log10);
470 ytr(im,DNAN); /* reset precalc */
471 log10_range = log10(im->maxval) - log10(im->minval);
473 /* make sure first y=10^x gridline is located on
474 integer pixel position by moving scale slightly
475 downwards (sub-pixel movement) */
476 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
477 ypixfrac = ypix - floor(ypix);
478 if (ypixfrac > 0 && ypixfrac < 1) {
479 double yfrac = ypixfrac / im->ysize;
480 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
481 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
482 ytr(im,DNAN); /* reset precalc */
485 /* Make sure we have an integer pixel distance between
486 each minor gridline */
487 double ypos1 = ytr(im, im->minval);
488 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
489 double y_pixel_delta = ypos1 - ypos2;
490 double factor = y_pixel_delta / floor(y_pixel_delta);
491 double new_range = factor * (im->maxval - im->minval);
492 double gridstep = im->ygrid_scale.gridstep;
493 double minor_y, minor_y_px, minor_y_px_frac;
494 im->maxval = im->minval + new_range;
495 ytr(im,DNAN); /* reset precalc */
496 /* make sure first minor gridline is on integer pixel y coord */
497 minor_y = gridstep * floor(im->minval / gridstep);
498 while (minor_y < im->minval)
500 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
501 minor_y_px_frac = minor_y_px - floor(minor_y_px);
502 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
503 double yfrac = minor_y_px_frac / im->ysize;
504 double range = im->maxval - im->minval;
505 im->minval = im->minval - yfrac * range;
506 im->maxval = im->maxval - yfrac * range;
507 ytr(im,DNAN); /* reset precalc */
509 calc_horizontal_grid(im); /* recalc with changed im->maxval */
513 /* reduce data reimplementation by Alex */
517 enum cf_en cf, /* which consolidation function ?*/
518 unsigned long cur_step, /* step the data currently is in */
519 time_t *start, /* start, end and step as requested ... */
520 time_t *end, /* ... by the application will be ... */
521 unsigned long *step, /* ... adjusted to represent reality */
522 unsigned long *ds_cnt, /* number of data sources in file */
523 rrd_value_t **data) /* two dimensional array containing the data */
525 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
526 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
527 rrd_value_t *srcptr,*dstptr;
529 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
532 row_cnt = ((*end)-(*start))/cur_step;
538 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
539 row_cnt,reduce_factor,*start,*end,cur_step);
540 for (col=0;col<row_cnt;col++) {
541 printf("time %10lu: ",*start+(col+1)*cur_step);
542 for (i=0;i<*ds_cnt;i++)
543 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
548 /* We have to combine [reduce_factor] rows of the source
549 ** into one row for the destination. Doing this we also
550 ** need to take care to combine the correct rows. First
551 ** alter the start and end time so that they are multiples
552 ** of the new step time. We cannot reduce the amount of
553 ** time so we have to move the end towards the future and
554 ** the start towards the past.
556 end_offset = (*end) % (*step);
557 start_offset = (*start) % (*step);
559 /* If there is a start offset (which cannot be more than
560 ** one destination row), skip the appropriate number of
561 ** source rows and one destination row. The appropriate
562 ** number is what we do know (start_offset/cur_step) of
563 ** the new interval (*step/cur_step aka reduce_factor).
566 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
567 printf("row_cnt before: %lu\n",row_cnt);
570 (*start) = (*start)-start_offset;
571 skiprows=reduce_factor-start_offset/cur_step;
572 srcptr+=skiprows* *ds_cnt;
573 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
577 printf("row_cnt between: %lu\n",row_cnt);
580 /* At the end we have some rows that are not going to be
581 ** used, the amount is end_offset/cur_step
584 (*end) = (*end)-end_offset+(*step);
585 skiprows = end_offset/cur_step;
589 printf("row_cnt after: %lu\n",row_cnt);
592 /* Sanity check: row_cnt should be multiple of reduce_factor */
593 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
595 if (row_cnt%reduce_factor) {
596 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
597 row_cnt,reduce_factor);
598 printf("BUG in reduce_data()\n");
602 /* Now combine reduce_factor intervals at a time
603 ** into one interval for the destination.
606 for (dst_row=0;(long int)row_cnt>=reduce_factor;dst_row++) {
607 for (col=0;col<(*ds_cnt);col++) {
608 rrd_value_t newval=DNAN;
609 unsigned long validval=0;
611 for (i=0;i<reduce_factor;i++) {
612 if (isnan(srcptr[i*(*ds_cnt)+col])) {
616 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
624 newval += srcptr[i*(*ds_cnt)+col];
627 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
630 /* an interval contains a failure if any subintervals contained a failure */
632 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
635 newval = srcptr[i*(*ds_cnt)+col];
640 if (validval == 0){newval = DNAN;} else{
658 srcptr+=(*ds_cnt)*reduce_factor;
659 row_cnt-=reduce_factor;
661 /* If we had to alter the endtime, we didn't have enough
662 ** source rows to fill the last row. Fill it with NaN.
664 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
666 row_cnt = ((*end)-(*start))/ *step;
668 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
669 row_cnt,*start,*end,*step);
670 for (col=0;col<row_cnt;col++) {
671 printf("time %10lu: ",*start+(col+1)*(*step));
672 for (i=0;i<*ds_cnt;i++)
673 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
680 /* get the data required for the graphs from the
684 data_fetch(image_desc_t *im )
689 /* pull the data from the rrd files ... */
690 for (i=0;i< (int)im->gdes_c;i++){
691 /* only GF_DEF elements fetch data */
692 if (im->gdes[i].gf != GF_DEF)
696 /* do we have it already ?*/
697 for (ii=0;ii<i;ii++) {
698 if (im->gdes[ii].gf != GF_DEF)
700 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
701 && (im->gdes[i].cf == im->gdes[ii].cf)
702 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
703 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
704 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
705 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
706 /* OK, the data is already there.
707 ** Just copy the header portion
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 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
713 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
714 im->gdes[i].data = im->gdes[ii].data;
715 im->gdes[i].data_first = 0;
722 unsigned long ft_step = im->gdes[i].step ; /* ft_step will record what we got from fetch */
724 if((rrd_fetch_fn(im->gdes[i].rrd,
730 &im->gdes[i].ds_namv,
731 &im->gdes[i].data)) == -1){
734 im->gdes[i].data_first = 1;
736 if (ft_step < im->gdes[i].step) {
737 reduce_data(im->gdes[i].cf_reduce,
745 im->gdes[i].step = ft_step;
749 /* lets see if the required data source is really there */
750 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
751 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
754 if (im->gdes[i].ds== -1){
755 rrd_set_error("No DS called '%s' in '%s'",
756 im->gdes[i].ds_nam,im->gdes[i].rrd);
764 /* evaluate the expressions in the CDEF functions */
766 /*************************************************************
768 *************************************************************/
771 find_var_wrapper(void *arg1, char *key)
773 return find_var((image_desc_t *) arg1, key);
776 /* find gdes containing var*/
778 find_var(image_desc_t *im, char *key){
780 for(ii=0;ii<im->gdes_c-1;ii++){
781 if((im->gdes[ii].gf == GF_DEF
782 || im->gdes[ii].gf == GF_VDEF
783 || im->gdes[ii].gf == GF_CDEF)
784 && (strcmp(im->gdes[ii].vname,key) == 0)){
791 /* find the largest common denominator for all the numbers
792 in the 0 terminated num array */
797 for (i=0;num[i+1]!=0;i++){
799 rest=num[i] % num[i+1];
800 num[i]=num[i+1]; num[i+1]=rest;
804 /* return i==0?num[i]:num[i-1]; */
808 /* run the rpn calculator on all the VDEF and CDEF arguments */
810 data_calc( image_desc_t *im){
814 long *steparray, rpi;
819 rpnstack_init(&rpnstack);
821 for (gdi=0;gdi<im->gdes_c;gdi++){
822 /* Look for GF_VDEF and GF_CDEF in the same loop,
823 * so CDEFs can use VDEFs and vice versa
825 switch (im->gdes[gdi].gf) {
829 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
831 /* remove current shift */
832 vdp->start -= vdp->shift;
833 vdp->end -= vdp->shift;
836 if (im->gdes[gdi].shidx >= 0)
837 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
840 vdp->shift = im->gdes[gdi].shval;
842 /* normalize shift to multiple of consolidated step */
843 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
846 vdp->start += vdp->shift;
847 vdp->end += vdp->shift;
851 /* A VDEF has no DS. This also signals other parts
852 * of rrdtool that this is a VDEF value, not a CDEF.
854 im->gdes[gdi].ds_cnt = 0;
855 if (vdef_calc(im,gdi)) {
856 rrd_set_error("Error processing VDEF '%s'"
859 rpnstack_free(&rpnstack);
864 im->gdes[gdi].ds_cnt = 1;
865 im->gdes[gdi].ds = 0;
866 im->gdes[gdi].data_first = 1;
867 im->gdes[gdi].start = 0;
868 im->gdes[gdi].end = 0;
873 /* Find the variables in the expression.
874 * - VDEF variables are substituted by their values
875 * and the opcode is changed into OP_NUMBER.
876 * - CDEF variables are analized for their step size,
877 * the lowest common denominator of all the step
878 * sizes of the data sources involved is calculated
879 * and the resulting number is the step size for the
880 * resulting data source.
882 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
883 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
884 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
885 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
886 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
888 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
890 im->gdes[ptr].vname);
891 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
893 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
894 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
895 } else { /* normal variables and PREF(variables) */
897 /* add one entry to the array that keeps track of the step sizes of the
898 * data sources going into the CDEF. */
900 rrd_realloc(steparray,
901 (++stepcnt+1)*sizeof(*steparray)))==NULL){
902 rrd_set_error("realloc steparray");
903 rpnstack_free(&rpnstack);
907 steparray[stepcnt-1] = im->gdes[ptr].step;
909 /* adjust start and end of cdef (gdi) so
910 * that it runs from the latest start point
911 * to the earliest endpoint of any of the
912 * rras involved (ptr)
915 if(im->gdes[gdi].start < im->gdes[ptr].start)
916 im->gdes[gdi].start = im->gdes[ptr].start;
918 if(im->gdes[gdi].end == 0 ||
919 im->gdes[gdi].end > im->gdes[ptr].end)
920 im->gdes[gdi].end = im->gdes[ptr].end;
922 /* store pointer to the first element of
923 * the rra providing data for variable,
924 * further save step size and data source
927 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
928 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
929 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
931 /* backoff the *.data ptr; this is done so
932 * rpncalc() function doesn't have to treat
933 * the first case differently
935 } /* if ds_cnt != 0 */
936 } /* if OP_VARIABLE */
937 } /* loop through all rpi */
939 /* move the data pointers to the correct period */
940 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
941 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
942 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
943 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
944 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
947 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
951 if(steparray == NULL){
952 rrd_set_error("rpn expressions without DEF"
953 " or CDEF variables are not supported");
954 rpnstack_free(&rpnstack);
957 steparray[stepcnt]=0;
958 /* Now find the resulting step. All steps in all
959 * used RRAs have to be visited
961 im->gdes[gdi].step = lcd(steparray);
963 if((im->gdes[gdi].data = malloc((
964 (im->gdes[gdi].end-im->gdes[gdi].start)
965 / im->gdes[gdi].step)
966 * sizeof(double)))==NULL){
967 rrd_set_error("malloc im->gdes[gdi].data");
968 rpnstack_free(&rpnstack);
972 /* Step through the new cdef results array and
973 * calculate the values
975 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
976 now<=im->gdes[gdi].end;
977 now += im->gdes[gdi].step)
979 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
981 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
982 * in this case we are advancing by timesteps;
983 * we use the fact that time_t is a synonym for long
985 if (rpn_calc(rpnp,&rpnstack,(long) now,
986 im->gdes[gdi].data,++dataidx) == -1) {
987 /* rpn_calc sets the error string */
988 rpnstack_free(&rpnstack);
991 } /* enumerate over time steps within a CDEF */
996 } /* enumerate over CDEFs */
997 rpnstack_free(&rpnstack);
1001 /* massage data so, that we get one value for each x coordinate in the graph */
1003 data_proc( image_desc_t *im ){
1005 double pixstep = (double)(im->end-im->start)
1006 /(double)im->xsize; /* how much time
1007 passes in one pixel */
1009 double minval=DNAN,maxval=DNAN;
1011 unsigned long gr_time;
1013 /* memory for the processed data */
1014 for(i=0;i<im->gdes_c;i++) {
1015 if((im->gdes[i].gf==GF_LINE) ||
1016 (im->gdes[i].gf==GF_AREA) ||
1017 (im->gdes[i].gf==GF_TICK)) {
1018 if((im->gdes[i].p_data = malloc((im->xsize +1)
1019 * sizeof(rrd_value_t)))==NULL){
1020 rrd_set_error("malloc data_proc");
1026 for (i=0;i<im->xsize;i++) { /* for each pixel */
1028 gr_time = im->start+pixstep*i; /* time of the current step */
1031 for (ii=0;ii<im->gdes_c;ii++) {
1033 switch (im->gdes[ii].gf) {
1037 if (!im->gdes[ii].stack)
1039 value = im->gdes[ii].yrule;
1040 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1041 /* The time of the data doesn't necessarily match
1042 ** the time of the graph. Beware.
1044 vidx = im->gdes[ii].vidx;
1045 if (im->gdes[vidx].gf == GF_VDEF) {
1046 value = im->gdes[vidx].vf.val;
1047 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1048 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1049 value = im->gdes[vidx].data[
1050 (unsigned long) floor(
1051 (double)(gr_time - im->gdes[vidx].start)
1052 / im->gdes[vidx].step)
1053 * im->gdes[vidx].ds_cnt
1061 if (! isnan(value)) {
1063 im->gdes[ii].p_data[i] = paintval;
1064 /* GF_TICK: the data values are not
1065 ** relevant for min and max
1067 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1068 if (isnan(minval) || paintval < minval)
1070 if (isnan(maxval) || paintval > maxval)
1074 im->gdes[ii].p_data[i] = DNAN;
1078 rrd_set_error("STACK should already be turned into LINE or AREA here");
1087 /* if min or max have not been asigned a value this is because
1088 there was no data in the graph ... this is not good ...
1089 lets set these to dummy values then ... */
1091 if (im->logarithmic) {
1092 if (isnan(minval)) minval = 0.2;
1093 if (isnan(maxval)) maxval = 5.1;
1096 if (isnan(minval)) minval = 0.0;
1097 if (isnan(maxval)) maxval = 1.0;
1100 /* adjust min and max values */
1101 if (isnan(im->minval)
1102 /* don't adjust low-end with log scale */ /* why not? */
1103 || ((!im->rigid) && im->minval > minval)
1105 if (im->logarithmic)
1106 im->minval = minval * 0.5;
1108 im->minval = minval;
1110 if (isnan(im->maxval)
1111 || (!im->rigid && im->maxval < maxval)
1113 if (im->logarithmic)
1114 im->maxval = maxval * 2.0;
1116 im->maxval = maxval;
1118 /* make sure min is smaller than max */
1119 if (im->minval > im->maxval) {
1120 im->minval = 0.99 * im->maxval;
1123 /* make sure min and max are not equal */
1124 if (im->minval == im->maxval) {
1126 if (! im->logarithmic) {
1129 /* make sure min and max are not both zero */
1130 if (im->maxval == 0.0) {
1139 /* identify the point where the first gridline, label ... gets placed */
1143 time_t start, /* what is the initial time */
1144 enum tmt_en baseint, /* what is the basic interval */
1145 long basestep /* how many if these do we jump a time */
1149 localtime_r(&start, &tm);
1152 tm.tm_sec -= tm.tm_sec % basestep; break;
1155 tm.tm_min -= tm.tm_min % basestep;
1160 tm.tm_hour -= tm.tm_hour % basestep; break;
1162 /* we do NOT look at the basestep for this ... */
1165 tm.tm_hour = 0; break;
1167 /* we do NOT look at the basestep for this ... */
1171 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1172 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1179 tm.tm_mon -= tm.tm_mon % basestep; break;
1187 tm.tm_year -= (tm.tm_year+1900) % basestep;
1192 /* identify the point where the next gridline, label ... gets placed */
1195 time_t current, /* what is the initial time */
1196 enum tmt_en baseint, /* what is the basic interval */
1197 long basestep /* how many if these do we jump a time */
1202 localtime_r(¤t, &tm);
1206 tm.tm_sec += basestep; break;
1208 tm.tm_min += basestep; break;
1210 tm.tm_hour += basestep; break;
1212 tm.tm_mday += basestep; break;
1214 tm.tm_mday += 7*basestep; break;
1216 tm.tm_mon += basestep; break;
1218 tm.tm_year += basestep;
1220 madetime = mktime(&tm);
1221 } while (madetime == -1); /* this is necessary to skip impssible times
1222 like the daylight saving time skips */
1228 /* calculate values required for PRINT and GPRINT functions */
1231 print_calc(image_desc_t *im, char ***prdata)
1233 long i,ii,validsteps;
1236 int graphelement = 0;
1239 double magfact = -1;
1243 /* wow initializing tmvdef is quite a task :-) */
1244 time_t now = time(NULL);
1245 localtime_r(&now,&tmvdef);
1246 if (im->imginfo) prlines++;
1247 for(i=0;i<im->gdes_c;i++){
1248 switch(im->gdes[i].gf){
1251 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1252 rrd_set_error("realloc prdata");
1256 /* PRINT and GPRINT can now print VDEF generated values.
1257 * There's no need to do any calculations on them as these
1258 * calculations were already made.
1260 vidx = im->gdes[i].vidx;
1261 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1262 printval = im->gdes[vidx].vf.val;
1263 localtime_r(&im->gdes[vidx].vf.when,&tmvdef);
1264 } else { /* need to calculate max,min,avg etcetera */
1265 max_ii =((im->gdes[vidx].end
1266 - im->gdes[vidx].start)
1267 / im->gdes[vidx].step
1268 * im->gdes[vidx].ds_cnt);
1271 for( ii=im->gdes[vidx].ds;
1273 ii+=im->gdes[vidx].ds_cnt){
1274 if (! finite(im->gdes[vidx].data[ii]))
1276 if (isnan(printval)){
1277 printval = im->gdes[vidx].data[ii];
1282 switch (im->gdes[i].cf){
1285 case CF_DEVSEASONAL:
1289 printval += im->gdes[vidx].data[ii];
1292 printval = min( printval, im->gdes[vidx].data[ii]);
1296 printval = max( printval, im->gdes[vidx].data[ii]);
1299 printval = im->gdes[vidx].data[ii];
1302 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1303 if (validsteps > 1) {
1304 printval = (printval / validsteps);
1307 } /* prepare printval */
1309 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1310 /* Magfact is set to -1 upon entry to print_calc. If it
1311 * is still less than 0, then we need to run auto_scale.
1312 * Otherwise, put the value into the correct units. If
1313 * the value is 0, then do not set the symbol or magnification
1314 * so next the calculation will be performed again. */
1315 if (magfact < 0.0) {
1316 auto_scale(im,&printval,&si_symb,&magfact);
1317 if (printval == 0.0)
1320 printval /= magfact;
1322 *(++percent_s) = 's';
1323 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1324 auto_scale(im,&printval,&si_symb,&magfact);
1327 if (im->gdes[i].gf == GF_PRINT){
1328 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1329 (*prdata)[prlines-1] = NULL;
1330 if (im->gdes[i].strftm){
1331 strftime((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1333 if (bad_format(im->gdes[i].format)) {
1334 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1338 #ifdef HAVE_SNPRINTF
1339 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1341 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1347 if (im->gdes[i].strftm){
1348 strftime(im->gdes[i].legend,FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1350 if (bad_format(im->gdes[i].format)) {
1351 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1354 #ifdef HAVE_SNPRINTF
1355 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1357 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1369 if(isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1370 im->gdes[i].yrule=im->gdes[im->gdes[i].vidx].vf.val;
1375 if(im->gdes[i].xrule == 0) { /* again ... the legend printer needs it*/
1376 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
1384 #ifdef WITH_PIECHART
1391 rrd_set_error("STACK should already be turned into LINE or AREA here");
1396 return graphelement;
1400 /* place legends with color spots */
1402 leg_place(image_desc_t *im)
1405 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1406 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1407 int fill=0, fill_last;
1409 int leg_x = border, leg_y = im->yimg;
1410 int leg_y_prev = im->yimg;
1414 char prt_fctn; /*special printfunctions */
1417 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1418 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1419 rrd_set_error("malloc for legspace");
1423 for(i=0;i<im->gdes_c;i++){
1426 /* hid legends for rules which are not displayed */
1428 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1429 if (im->gdes[i].gf == GF_HRULE &&
1430 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1431 im->gdes[i].legend[0] = '\0';
1433 if (im->gdes[i].gf == GF_VRULE &&
1434 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1435 im->gdes[i].legend[0] = '\0';
1438 leg_cc = strlen(im->gdes[i].legend);
1440 /* is there a controle code ant the end of the legend string ? */
1441 /* and it is not a tab \\t */
1442 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1443 prt_fctn = im->gdes[i].legend[leg_cc-1];
1445 im->gdes[i].legend[leg_cc] = '\0';
1449 /* remove exess space */
1450 while (prt_fctn=='g' &&
1452 im->gdes[i].legend[leg_cc-1]==' '){
1454 im->gdes[i].legend[leg_cc]='\0';
1457 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1460 /* no interleg space if string ends in \g */
1461 fill += legspace[i];
1463 fill += gfx_get_text_width(im->canvas, fill+border,
1464 im->text_prop[TEXT_PROP_LEGEND].font,
1465 im->text_prop[TEXT_PROP_LEGEND].size,
1467 im->gdes[i].legend, 0);
1472 /* who said there was a special tag ... ?*/
1473 if (prt_fctn=='g') {
1476 if (prt_fctn == '\0') {
1477 if (i == im->gdes_c -1 ) prt_fctn ='l';
1479 /* is it time to place the legends ? */
1480 if (fill > im->ximg - 2*border){
1495 if (prt_fctn != '\0'){
1497 if (leg_c >= 2 && prt_fctn == 'j') {
1498 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1502 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1503 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1505 for(ii=mark;ii<=i;ii++){
1506 if(im->gdes[ii].legend[0]=='\0')
1507 continue; /* skip empty legends */
1508 im->gdes[ii].leg_x = leg_x;
1509 im->gdes[ii].leg_y = leg_y;
1511 gfx_get_text_width(im->canvas, leg_x,
1512 im->text_prop[TEXT_PROP_LEGEND].font,
1513 im->text_prop[TEXT_PROP_LEGEND].size,
1515 im->gdes[ii].legend, 0)
1520 /* only add y space if there was text on the line */
1521 if (leg_x > border || prt_fctn == 's')
1522 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1523 if (prt_fctn == 's')
1524 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1530 im->yimg = leg_y_prev;
1531 /* if we did place some legends we have to add vertical space */
1532 if (leg_y != im->yimg){
1533 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1540 /* create a grid on the graph. it determines what to do
1541 from the values of xsize, start and end */
1543 /* the xaxis labels are determined from the number of seconds per pixel
1544 in the requested graph */
1549 calc_horizontal_grid(image_desc_t *im)
1555 int decimals, fractionals;
1557 im->ygrid_scale.labfact=2;
1558 range = im->maxval - im->minval;
1559 scaledrange = range / im->magfact;
1561 /* does the scale of this graph make it impossible to put lines
1562 on it? If so, give up. */
1563 if (isnan(scaledrange)) {
1567 /* find grid spaceing */
1569 if(isnan(im->ygridstep)){
1570 if(im->extra_flags & ALTYGRID) {
1571 /* find the value with max number of digits. Get number of digits */
1572 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1573 if(decimals <= 0) /* everything is small. make place for zero */
1576 im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1578 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1579 im->ygrid_scale.gridstep = 0.1;
1580 /* should have at least 5 lines but no more then 15 */
1581 if(range/im->ygrid_scale.gridstep < 5)
1582 im->ygrid_scale.gridstep /= 10;
1583 if(range/im->ygrid_scale.gridstep > 15)
1584 im->ygrid_scale.gridstep *= 10;
1585 if(range/im->ygrid_scale.gridstep > 5) {
1586 im->ygrid_scale.labfact = 1;
1587 if(range/im->ygrid_scale.gridstep > 8)
1588 im->ygrid_scale.labfact = 2;
1591 im->ygrid_scale.gridstep /= 5;
1592 im->ygrid_scale.labfact = 5;
1594 fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1595 if(fractionals < 0) { /* small amplitude. */
1596 int len = decimals - fractionals + 1;
1597 if (im->unitslength < len+2) im->unitslength = len+2;
1598 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1600 int len = decimals + 1;
1601 if (im->unitslength < len+2) im->unitslength = len+2;
1602 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1606 for(i=0;ylab[i].grid > 0;i++){
1607 pixel = im->ysize / (scaledrange / ylab[i].grid);
1614 if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1615 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1620 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1623 im->ygrid_scale.gridstep = im->ygridstep;
1624 im->ygrid_scale.labfact = im->ylabfact;
1629 int draw_horizontal_grid(image_desc_t *im)
1633 char graph_label[100];
1635 double X0=im->xorigin;
1636 double X1=im->xorigin+im->xsize;
1638 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1639 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1641 scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1642 MaxY = scaledstep*(double)egrid;
1643 for (i = sgrid; i <= egrid; i++){
1644 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1645 double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
1646 if ( Y0 >= im->yorigin-im->ysize
1647 && Y0 <= im->yorigin){
1648 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1649 with the chosen settings. Add a label if required by settings, or if
1650 there is only one label so far and the next grid line is out of bounds. */
1651 if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){
1652 if (im->symbol == ' ') {
1653 if(im->extra_flags & ALTYGRID) {
1654 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1657 sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1659 sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1663 char sisym = ( i == 0 ? ' ' : im->symbol);
1664 if(im->extra_flags & ALTYGRID) {
1665 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1668 sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1670 sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1676 gfx_new_text ( im->canvas,
1677 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1678 im->graph_col[GRC_FONT],
1679 im->text_prop[TEXT_PROP_AXIS].font,
1680 im->text_prop[TEXT_PROP_AXIS].size,
1681 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1683 gfx_new_dashed_line ( im->canvas,
1686 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1687 im->grid_dash_on, im->grid_dash_off);
1689 } else if (!(im->extra_flags & NOMINOR)) {
1690 gfx_new_dashed_line ( im->canvas,
1693 GRIDWIDTH, im->graph_col[GRC_GRID],
1694 im->grid_dash_on, im->grid_dash_off);
1702 /* this is frexp for base 10 */
1703 double frexp10(double, double *);
1704 double frexp10(double x, double *e) {
1708 iexp = floor(log(fabs(x)) / log(10));
1709 mnt = x / pow(10.0, iexp);
1712 mnt = x / pow(10.0, iexp);
1718 /* logaritmic horizontal grid */
1720 horizontal_log_grid(image_desc_t *im)
1722 double yloglab[][10] = {
1723 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1724 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1725 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1726 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1727 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.}};
1729 int i, j, val_exp, min_exp;
1730 double nex; /* number of decades in data */
1731 double logscale; /* scale in logarithmic space */
1732 int exfrac = 1; /* decade spacing */
1733 int mid = -1; /* row in yloglab for major grid */
1734 double mspac; /* smallest major grid spacing (pixels) */
1735 int flab; /* first value in yloglab to use */
1738 char graph_label[100];
1740 nex = log10(im->maxval / im->minval);
1741 logscale = im->ysize / nex;
1743 /* major spacing for data with high dynamic range */
1744 while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1745 if(exfrac == 1) exfrac = 3;
1749 /* major spacing for less dynamic data */
1751 /* search best row in yloglab */
1753 for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
1754 mspac = logscale * log10(10.0 / yloglab[mid][i]);
1755 } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && mid < 5);
1758 /* find first value in yloglab */
1759 for(flab = 0; frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
1760 if(yloglab[mid][flab] == 10.0) {
1765 if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
1768 X1=im->xorigin+im->xsize;
1772 value = yloglab[mid][flab] * pow(10.0, val_exp);
1774 Y0 = ytr(im, value);
1775 if(Y0 <= im->yorigin - im->ysize) break;
1777 /* major grid line */
1778 gfx_new_dashed_line ( im->canvas,
1781 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1782 im->grid_dash_on, im->grid_dash_off);
1785 if (im->extra_flags & FORCE_UNITS_SI) {
1790 scale = floor(val_exp / 3.0);
1791 if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
1792 else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
1793 pvalue *= yloglab[mid][flab];
1795 if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
1796 ((scale+si_symbcenter) >= 0) )
1797 symbol = si_symbol[scale+si_symbcenter];
1801 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1803 sprintf(graph_label,"%3.0e", value);
1804 gfx_new_text ( im->canvas,
1805 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1806 im->graph_col[GRC_FONT],
1807 im->text_prop[TEXT_PROP_AXIS].font,
1808 im->text_prop[TEXT_PROP_AXIS].size,
1809 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1813 if(mid < 4 && exfrac == 1) {
1814 /* find first and last minor line behind current major line
1815 * i is the first line and j tha last */
1817 min_exp = val_exp - 1;
1818 for(i = 1; yloglab[mid][i] < 10.0; i++);
1819 i = yloglab[mid][i - 1] + 1;
1824 i = yloglab[mid][flab - 1] + 1;
1825 j = yloglab[mid][flab];
1828 /* draw minor lines below current major line */
1831 value = i * pow(10.0, min_exp);
1832 if(value < im->minval) continue;
1834 Y0 = ytr(im, value);
1835 if(Y0 <= im->yorigin - im->ysize) break;
1838 gfx_new_dashed_line ( im->canvas,
1841 GRIDWIDTH, im->graph_col[GRC_GRID],
1842 im->grid_dash_on, im->grid_dash_off);
1845 else if(exfrac > 1) {
1846 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1847 value = pow(10.0, i);
1848 if(value < im->minval) continue;
1850 Y0 = ytr(im, value);
1851 if(Y0 <= im->yorigin - im->ysize) break;
1854 gfx_new_dashed_line ( im->canvas,
1857 GRIDWIDTH, im->graph_col[GRC_GRID],
1858 im->grid_dash_on, im->grid_dash_off);
1863 if(yloglab[mid][++flab] == 10.0) {
1869 /* draw minor lines after highest major line */
1870 if(mid < 4 && exfrac == 1) {
1871 /* find first and last minor line below current major line
1872 * i is the first line and j tha last */
1874 min_exp = val_exp - 1;
1875 for(i = 1; yloglab[mid][i] < 10.0; i++);
1876 i = yloglab[mid][i - 1] + 1;
1881 i = yloglab[mid][flab - 1] + 1;
1882 j = yloglab[mid][flab];
1885 /* draw minor lines below current major line */
1888 value = i * pow(10.0, min_exp);
1889 if(value < im->minval) continue;
1891 Y0 = ytr(im, value);
1892 if(Y0 <= im->yorigin - im->ysize) break;
1895 gfx_new_dashed_line ( im->canvas,
1898 GRIDWIDTH, im->graph_col[GRC_GRID],
1899 im->grid_dash_on, im->grid_dash_off);
1902 /* fancy minor gridlines */
1903 else if(exfrac > 1) {
1904 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1905 value = pow(10.0, i);
1906 if(value < im->minval) continue;
1908 Y0 = ytr(im, value);
1909 if(Y0 <= im->yorigin - im->ysize) break;
1912 gfx_new_dashed_line ( im->canvas,
1915 GRIDWIDTH, im->graph_col[GRC_GRID],
1916 im->grid_dash_on, im->grid_dash_off);
1928 int xlab_sel; /* which sort of label and grid ? */
1929 time_t ti, tilab, timajor;
1931 char graph_label[100];
1932 double X0,Y0,Y1; /* points for filled graph and more*/
1935 /* the type of time grid is determined by finding
1936 the number of seconds per pixel in the graph */
1939 if(im->xlab_user.minsec == -1){
1940 factor=(im->end - im->start)/im->xsize;
1942 while ( xlab[xlab_sel+1].minsec != -1
1943 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; } /* pick the last one */
1944 while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
1945 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; } /* go back to the smallest size */
1946 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1947 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1948 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1949 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1950 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1951 im->xlab_user.labst = xlab[xlab_sel].labst;
1952 im->xlab_user.precis = xlab[xlab_sel].precis;
1953 im->xlab_user.stst = xlab[xlab_sel].stst;
1956 /* y coords are the same for every line ... */
1958 Y1 = im->yorigin-im->ysize;
1961 /* paint the minor grid */
1962 if (!(im->extra_flags & NOMINOR))
1964 for(ti = find_first_time(im->start,
1965 im->xlab_user.gridtm,
1966 im->xlab_user.gridst),
1967 timajor = find_first_time(im->start,
1968 im->xlab_user.mgridtm,
1969 im->xlab_user.mgridst);
1971 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1973 /* are we inside the graph ? */
1974 if (ti < im->start || ti > im->end) continue;
1975 while (timajor < ti) {
1976 timajor = find_next_time(timajor,
1977 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1979 if (ti == timajor) continue; /* skip as falls on major grid line */
1981 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1982 im->graph_col[GRC_GRID],
1983 im->grid_dash_on, im->grid_dash_off);
1988 /* paint the major grid */
1989 for(ti = find_first_time(im->start,
1990 im->xlab_user.mgridtm,
1991 im->xlab_user.mgridst);
1993 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1995 /* are we inside the graph ? */
1996 if (ti < im->start || ti > im->end) continue;
1998 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1999 im->graph_col[GRC_MGRID],
2000 im->grid_dash_on, im->grid_dash_off);
2003 /* paint the labels below the graph */
2004 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
2005 im->xlab_user.labtm,
2006 im->xlab_user.labst);
2007 ti <= im->end - im->xlab_user.precis/2;
2008 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
2010 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
2011 /* are we inside the graph ? */
2012 if (tilab < im->start || tilab > im->end) continue;
2015 localtime_r(&tilab, &tm);
2016 strftime(graph_label,99,im->xlab_user.stst, &tm);
2018 # error "your libc has no strftime I guess we'll abort the exercise here."
2020 gfx_new_text ( im->canvas,
2021 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
2022 im->graph_col[GRC_FONT],
2023 im->text_prop[TEXT_PROP_AXIS].font,
2024 im->text_prop[TEXT_PROP_AXIS].size,
2025 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
2038 /* draw x and y axis */
2039 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2040 im->xorigin+im->xsize,im->yorigin-im->ysize,
2041 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2043 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2044 im->xorigin+im->xsize,im->yorigin-im->ysize,
2045 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2047 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
2048 im->xorigin+im->xsize+4,im->yorigin,
2049 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2051 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
2052 im->xorigin,im->yorigin-im->ysize-4,
2053 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2056 /* arrow for X and Y axis direction */
2057 gfx_new_area ( im->canvas,
2058 im->xorigin+im->xsize+2, im->yorigin-2,
2059 im->xorigin+im->xsize+2, im->yorigin+3,
2060 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
2061 im->graph_col[GRC_ARROW]);
2063 gfx_new_area ( im->canvas,
2064 im->xorigin-2, im->yorigin-im->ysize-2,
2065 im->xorigin+3, im->yorigin-im->ysize-2,
2066 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
2067 im->graph_col[GRC_ARROW]);
2072 grid_paint(image_desc_t *im)
2076 double X0,Y0; /* points for filled graph and more*/
2079 /* draw 3d border */
2080 node = gfx_new_area (im->canvas, 0,im->yimg,
2082 2,2,im->graph_col[GRC_SHADEA]);
2083 gfx_add_point( node , im->ximg - 2, 2 );
2084 gfx_add_point( node , im->ximg, 0 );
2085 gfx_add_point( node , 0,0 );
2086 /* gfx_add_point( node , 0,im->yimg ); */
2088 node = gfx_new_area (im->canvas, 2,im->yimg-2,
2089 im->ximg-2,im->yimg-2,
2091 im->graph_col[GRC_SHADEB]);
2092 gfx_add_point( node , im->ximg,0);
2093 gfx_add_point( node , im->ximg,im->yimg);
2094 gfx_add_point( node , 0,im->yimg);
2095 /* gfx_add_point( node , 0,im->yimg ); */
2098 if (im->draw_x_grid == 1 )
2101 if (im->draw_y_grid == 1){
2102 if(im->logarithmic){
2103 res = horizontal_log_grid(im);
2105 res = draw_horizontal_grid(im);
2108 /* dont draw horizontal grid if there is no min and max val */
2110 char *nodata = "No Data found";
2111 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
2112 im->graph_col[GRC_FONT],
2113 im->text_prop[TEXT_PROP_AXIS].font,
2114 im->text_prop[TEXT_PROP_AXIS].size,
2115 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
2120 /* yaxis unit description */
2121 gfx_new_text( im->canvas,
2122 10, (im->yorigin - im->ysize/2),
2123 im->graph_col[GRC_FONT],
2124 im->text_prop[TEXT_PROP_UNIT].font,
2125 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2126 RRDGRAPH_YLEGEND_ANGLE,
2127 GFX_H_LEFT, GFX_V_CENTER,
2131 gfx_new_text( im->canvas,
2132 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2133 im->graph_col[GRC_FONT],
2134 im->text_prop[TEXT_PROP_TITLE].font,
2135 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2136 GFX_H_CENTER, GFX_V_CENTER,
2138 /* rrdtool 'logo' */
2139 gfx_new_text( im->canvas,
2141 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2142 im->text_prop[TEXT_PROP_AXIS].font,
2143 5.5, im->tabwidth, 270,
2144 GFX_H_RIGHT, GFX_V_TOP,
2145 "RRDTOOL / TOBI OETIKER");
2147 /* graph watermark */
2148 if(im->watermark[0] != '\0') {
2149 gfx_new_text( im->canvas,
2150 im->ximg/2, im->yimg-6,
2151 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2152 im->text_prop[TEXT_PROP_AXIS].font,
2153 5.5, im->tabwidth, 0,
2154 GFX_H_CENTER, GFX_V_BOTTOM,
2159 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2160 for(i=0;i<im->gdes_c;i++){
2161 if(im->gdes[i].legend[0] =='\0')
2164 /* im->gdes[i].leg_y is the bottom of the legend */
2165 X0 = im->gdes[i].leg_x;
2166 Y0 = im->gdes[i].leg_y;
2167 gfx_new_text ( im->canvas, X0, Y0,
2168 im->graph_col[GRC_FONT],
2169 im->text_prop[TEXT_PROP_LEGEND].font,
2170 im->text_prop[TEXT_PROP_LEGEND].size,
2171 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2172 im->gdes[i].legend );
2173 /* The legend for GRAPH items starts with "M " to have
2174 enough space for the box */
2175 if ( im->gdes[i].gf != GF_PRINT &&
2176 im->gdes[i].gf != GF_GPRINT &&
2177 im->gdes[i].gf != GF_COMMENT) {
2180 boxH = gfx_get_text_width(im->canvas, 0,
2181 im->text_prop[TEXT_PROP_LEGEND].font,
2182 im->text_prop[TEXT_PROP_LEGEND].size,
2183 im->tabwidth,"o", 0) * 1.2;
2186 /* make sure transparent colors show up the same way as in the graph */
2187 node = gfx_new_area(im->canvas,
2191 im->graph_col[GRC_BACK]);
2192 gfx_add_point ( node, X0+boxH, Y0-boxV );
2194 node = gfx_new_area(im->canvas,
2199 gfx_add_point ( node, X0+boxH, Y0-boxV );
2200 node = gfx_new_line(im->canvas,
2203 1.0,im->graph_col[GRC_FRAME]);
2204 gfx_add_point(node,X0+boxH,Y0);
2205 gfx_add_point(node,X0+boxH,Y0-boxV);
2206 gfx_close_path(node);
2213 /*****************************************************
2214 * lazy check make sure we rely need to create this graph
2215 *****************************************************/
2217 int lazy_check(image_desc_t *im){
2220 struct stat imgstat;
2222 if (im->lazy == 0) return 0; /* no lazy option */
2223 if (stat(im->graphfile,&imgstat) != 0)
2224 return 0; /* can't stat */
2225 /* one pixel in the existing graph is more then what we would
2227 if (time(NULL) - imgstat.st_mtime >
2228 (im->end - im->start) / im->xsize)
2230 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2231 return 0; /* the file does not exist */
2232 switch (im->canvas->imgformat) {
2234 size = PngSize(fd,&(im->ximg),&(im->yimg));
2243 #ifdef WITH_PIECHART
2245 pie_part(image_desc_t *im, gfx_color_t color,
2246 double PieCenterX, double PieCenterY, double Radius,
2247 double startangle, double endangle)
2251 double step=M_PI/50; /* Number of iterations for the circle;
2252 ** 10 is definitely too low, more than
2253 ** 50 seems to be overkill
2256 /* Strange but true: we have to work clockwise or else
2257 ** anti aliasing nor transparency don't work.
2259 ** This test is here to make sure we do it right, also
2260 ** this makes the for...next loop more easy to implement.
2261 ** The return will occur if the user enters a negative number
2262 ** (which shouldn't be done according to the specs) or if the
2263 ** programmers do something wrong (which, as we all know, never
2264 ** happens anyway :)
2266 if (endangle<startangle) return;
2268 /* Hidden feature: Radius decreases each full circle */
2270 while (angle>=2*M_PI) {
2275 node=gfx_new_area(im->canvas,
2276 PieCenterX+sin(startangle)*Radius,
2277 PieCenterY-cos(startangle)*Radius,
2280 PieCenterX+sin(endangle)*Radius,
2281 PieCenterY-cos(endangle)*Radius,
2283 for (angle=endangle;angle-startangle>=step;angle-=step) {
2285 PieCenterX+sin(angle)*Radius,
2286 PieCenterY-cos(angle)*Radius );
2293 graph_size_location(image_desc_t *im, int elements
2295 #ifdef WITH_PIECHART
2301 /* The actual size of the image to draw is determined from
2302 ** several sources. The size given on the command line is
2303 ** the graph area but we need more as we have to draw labels
2304 ** and other things outside the graph area
2307 /* +-+-------------------------------------------+
2308 ** |l|.................title.....................|
2309 ** |e+--+-------------------------------+--------+
2312 ** |l| l| main graph area | chart |
2315 ** |r+--+-------------------------------+--------+
2316 ** |e| | x-axis labels | |
2317 ** |v+--+-------------------------------+--------+
2318 ** | |..............legends......................|
2319 ** +-+-------------------------------------------+
2321 ** +---------------------------------------------+
2327 #ifdef WITH_PIECHART
2332 Xlegend =0, Ylegend =0,
2334 Xspacing =15, Yspacing =15,
2338 if (im->extra_flags & ONLY_GRAPH) {
2340 im->ximg = im->xsize;
2341 im->yimg = im->ysize;
2342 im->yorigin = im->ysize;
2347 if (im->ylegend[0] != '\0' ) {
2348 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2352 if (im->title[0] != '\0') {
2353 /* The title is placed "inbetween" two text lines so it
2354 ** automatically has some vertical spacing. The horizontal
2355 ** spacing is added here, on each side.
2357 /* don't care for the with of the title
2358 Xtitle = gfx_get_text_width(im->canvas, 0,
2359 im->text_prop[TEXT_PROP_TITLE].font,
2360 im->text_prop[TEXT_PROP_TITLE].size,
2362 im->title, 0) + 2*Xspacing; */
2363 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2369 if (im->draw_x_grid) {
2370 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2372 if (im->draw_y_grid) {
2373 Xylabel=gfx_get_text_width(im->canvas, 0,
2374 im->text_prop[TEXT_PROP_AXIS].font,
2375 im->text_prop[TEXT_PROP_AXIS].size,
2377 "0", 0) * im->unitslength;
2381 #ifdef WITH_PIECHART
2383 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2389 /* Now calculate the total size. Insert some spacing where
2390 desired. im->xorigin and im->yorigin need to correspond
2391 with the lower left corner of the main graph area or, if
2392 this one is not set, the imaginary box surrounding the
2395 /* The legend width cannot yet be determined, as a result we
2396 ** have problems adjusting the image to it. For now, we just
2397 ** forget about it at all; the legend will have to fit in the
2398 ** size already allocated.
2400 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2402 #ifdef WITH_PIECHART
2406 if (Xmain) im->ximg += Xspacing;
2407 #ifdef WITH_PIECHART
2408 if (Xpie) im->ximg += Xspacing;
2411 im->xorigin = Xspacing + Xylabel;
2413 /* the length of the title should not influence with width of the graph
2414 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2416 if (Xvertical) { /* unit description */
2417 im->ximg += Xvertical;
2418 im->xorigin += Xvertical;
2422 /* The vertical size is interesting... we need to compare
2423 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2424 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2425 ** in order to start even thinking about Ylegend or Ywatermark.
2427 ** Do it in three portions: First calculate the inner part,
2428 ** then do the legend, then adjust the total height of the img,
2429 ** adding space for a watermark if one exists;
2432 /* reserve space for main and/or pie */
2434 im->yimg = Ymain + Yxlabel;
2436 #ifdef WITH_PIECHART
2437 if (im->yimg < Ypie) im->yimg = Ypie;
2440 im->yorigin = im->yimg - Yxlabel;
2442 /* reserve space for the title *or* some padding above the graph */
2445 im->yorigin += Ytitle;
2447 im->yimg += 1.5*Yspacing;
2448 im->yorigin += 1.5*Yspacing;
2450 /* reserve space for padding below the graph */
2451 im->yimg += Yspacing;
2453 /* Determine where to place the legends onto the image.
2454 ** Adjust im->yimg to match the space requirements.
2456 if(leg_place(im)==-1)
2459 if (im->watermark[0] != '\0') {
2460 im->yimg += Ywatermark;
2464 if (Xlegend > im->ximg) {
2466 /* reposition Pie */
2470 #ifdef WITH_PIECHART
2471 /* The pie is placed in the upper right hand corner,
2472 ** just below the title (if any) and with sufficient
2476 im->pie_x = im->ximg - Xspacing - Xpie/2;
2477 im->pie_y = im->yorigin-Ymain+Ypie/2;
2479 im->pie_x = im->ximg/2;
2480 im->pie_y = im->yorigin-Ypie/2;
2488 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2489 /* yes we are loosing precision by doing tos with floats instead of doubles
2490 but it seems more stable this way. */
2492 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
2495 int aInt = *(int*)&A;
2496 int bInt = *(int*)&B;
2498 /* Make sure maxUlps is non-negative and small enough that the
2499 default NAN won't compare as equal to anything. */
2501 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
2503 /* Make aInt lexicographically ordered as a twos-complement int */
2506 aInt = 0x80000000l - aInt;
2508 /* Make bInt lexicographically ordered as a twos-complement int */
2511 bInt = 0x80000000l - bInt;
2513 intDiff = abs(aInt - bInt);
2515 if (intDiff <= maxUlps)
2521 /* draw that picture thing ... */
2523 graph_paint(image_desc_t *im, char ***calcpr)
2526 int lazy = lazy_check(im);
2527 #ifdef WITH_PIECHART
2529 double PieStart=0.0;
2534 double areazero = 0.0;
2535 graph_desc_t *lastgdes = NULL;
2537 /* if we are lazy and there is nothing to PRINT ... quit now */
2538 if (lazy && im->prt_c==0) return 0;
2540 /* pull the data from the rrd files ... */
2542 if(data_fetch(im)==-1)
2545 /* evaluate VDEF and CDEF operations ... */
2546 if(data_calc(im)==-1)
2549 #ifdef WITH_PIECHART
2550 /* check if we need to draw a piechart */
2551 for(i=0;i<im->gdes_c;i++){
2552 if (im->gdes[i].gf == GF_PART) {
2559 /* calculate and PRINT and GPRINT definitions. We have to do it at
2560 * this point because it will affect the length of the legends
2561 * if there are no graph elements we stop here ...
2562 * if we are lazy, try to quit ...
2564 i=print_calc(im,calcpr);
2567 #ifdef WITH_PIECHART
2570 ) || lazy) return 0;
2572 #ifdef WITH_PIECHART
2573 /* If there's only the pie chart to draw, signal this */
2574 if (i==0) piechart=2;
2577 /* get actual drawing data and find min and max values*/
2578 if(data_proc(im)==-1)
2581 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2583 if(!im->rigid && ! im->logarithmic)
2584 expand_range(im); /* make sure the upper and lower limit are
2587 if (!calc_horizontal_grid(im))
2594 /**************************************************************
2595 *** Calculating sizes and locations became a bit confusing ***
2596 *** so I moved this into a separate function. ***
2597 **************************************************************/
2598 if(graph_size_location(im,i
2599 #ifdef WITH_PIECHART
2605 /* the actual graph is created by going through the individual
2606 graph elements and then drawing them */
2608 node=gfx_new_area ( im->canvas,
2612 im->graph_col[GRC_BACK]);
2614 gfx_add_point(node,im->ximg, 0);
2616 #ifdef WITH_PIECHART
2617 if (piechart != 2) {
2619 node=gfx_new_area ( im->canvas,
2620 im->xorigin, im->yorigin,
2621 im->xorigin + im->xsize, im->yorigin,
2622 im->xorigin + im->xsize, im->yorigin-im->ysize,
2623 im->graph_col[GRC_CANVAS]);
2625 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2627 if (im->minval > 0.0)
2628 areazero = im->minval;
2629 if (im->maxval < 0.0)
2630 areazero = im->maxval;
2631 #ifdef WITH_PIECHART
2635 #ifdef WITH_PIECHART
2637 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2641 for(i=0;i<im->gdes_c;i++){
2642 switch(im->gdes[i].gf){
2655 for (ii = 0; ii < im->xsize; ii++)
2657 if (!isnan(im->gdes[i].p_data[ii]) &&
2658 im->gdes[i].p_data[ii] != 0.0)
2660 if (im -> gdes[i].yrule > 0 ) {
2661 gfx_new_line(im->canvas,
2662 im -> xorigin + ii, im->yorigin,
2663 im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2665 im -> gdes[i].col );
2666 } else if ( im -> gdes[i].yrule < 0 ) {
2667 gfx_new_line(im->canvas,
2668 im -> xorigin + ii, im->yorigin - im -> ysize,
2669 im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2671 im -> gdes[i].col );
2679 /* fix data points at oo and -oo */
2680 for(ii=0;ii<im->xsize;ii++){
2681 if (isinf(im->gdes[i].p_data[ii])){
2682 if (im->gdes[i].p_data[ii] > 0) {
2683 im->gdes[i].p_data[ii] = im->maxval ;
2685 im->gdes[i].p_data[ii] = im->minval ;
2691 /* *******************************************************
2696 -------|--t-1--t--------------------------------
2698 if we know the value at time t was a then
2699 we draw a square from t-1 to t with the value a.
2701 ********************************************************* */
2702 if (im->gdes[i].col != 0x0){
2703 /* GF_LINE and friend */
2704 if(im->gdes[i].gf == GF_LINE ){
2707 for(ii=1;ii<im->xsize;ii++){
2708 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2712 if ( node == NULL ) {
2713 last_y = ytr(im,im->gdes[i].p_data[ii]);
2714 if ( im->slopemode == 0 ){
2715 node = gfx_new_line(im->canvas,
2716 ii-1+im->xorigin,last_y,
2717 ii+im->xorigin,last_y,
2718 im->gdes[i].linewidth,
2721 node = gfx_new_line(im->canvas,
2722 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2723 ii+im->xorigin,last_y,
2724 im->gdes[i].linewidth,
2728 double new_y = ytr(im,im->gdes[i].p_data[ii]);
2729 if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2730 gfx_add_point(node,ii-1+im->xorigin,new_y);
2733 gfx_add_point(node,ii+im->xorigin,new_y);
2739 double *foreY=malloc(sizeof(double)*im->xsize*2);
2740 double *foreX=malloc(sizeof(double)*im->xsize*2);
2741 double *backY=malloc(sizeof(double)*im->xsize*2);
2742 double *backX=malloc(sizeof(double)*im->xsize*2);
2744 for(ii=0;ii<=im->xsize;ii++){
2746 if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2749 while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2750 node = gfx_new_area(im->canvas,
2753 foreX[cntI],foreY[cntI], im->gdes[i].col);
2754 while (cntI < idxI) {
2757 while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2758 gfx_add_point(node,foreX[cntI],foreY[cntI]);
2760 gfx_add_point(node,backX[idxI],backY[idxI]);
2764 while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;}
2765 gfx_add_point(node,backX[idxI],backY[idxI]);
2774 if (ii == im->xsize) break;
2776 /* keep things simple for now, just draw these bars
2777 do not try to build a big and complex area */
2780 if ( im->slopemode == 0 && ii==0){
2783 if ( isnan(im->gdes[i].p_data[ii]) ) {
2787 ytop = ytr(im,im->gdes[i].p_data[ii]);
2788 if ( lastgdes && im->gdes[i].stack ) {
2789 ybase = ytr(im,lastgdes->p_data[ii]);
2791 ybase = ytr(im,areazero);
2793 if ( ybase == ytop ){
2797 /* every area has to be wound clock-wise,
2798 so we have to make sur base remains base */
2800 double extra = ytop;
2804 if ( im->slopemode == 0 ){
2805 backY[++idxI] = ybase-0.2;
2806 backX[idxI] = ii+im->xorigin-1;
2807 foreY[idxI] = ytop+0.2;
2808 foreX[idxI] = ii+im->xorigin-1;
2810 backY[++idxI] = ybase-0.2;
2811 backX[idxI] = ii+im->xorigin;
2812 foreY[idxI] = ytop+0.2;
2813 foreX[idxI] = ii+im->xorigin;
2815 /* close up any remaining area */
2820 } /* else GF_LINE */
2821 } /* if color != 0x0 */
2822 /* make sure we do not run into trouble when stacking on NaN */
2823 for(ii=0;ii<im->xsize;ii++){
2824 if (isnan(im->gdes[i].p_data[ii])) {
2825 if (lastgdes && (im->gdes[i].stack)) {
2826 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2828 im->gdes[i].p_data[ii] = areazero;
2832 lastgdes = &(im->gdes[i]);
2834 #ifdef WITH_PIECHART
2836 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2837 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2839 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2840 pie_part(im,im->gdes[i].col,
2841 im->pie_x,im->pie_y,im->piesize*0.4,
2842 M_PI*2.0*PieStart/100.0,
2843 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2844 PieStart += im->gdes[i].yrule;
2849 rrd_set_error("STACK should already be turned into LINE or AREA here");
2855 #ifdef WITH_PIECHART
2863 /* grid_paint also does the text */
2864 if( !(im->extra_flags & ONLY_GRAPH) )
2868 if( !(im->extra_flags & ONLY_GRAPH) )
2871 /* the RULES are the last thing to paint ... */
2872 for(i=0;i<im->gdes_c;i++){
2874 switch(im->gdes[i].gf){
2876 if(im->gdes[i].yrule >= im->minval
2877 && im->gdes[i].yrule <= im->maxval)
2878 gfx_new_line(im->canvas,
2879 im->xorigin,ytr(im,im->gdes[i].yrule),
2880 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2881 1.0,im->gdes[i].col);
2884 if(im->gdes[i].xrule >= im->start
2885 && im->gdes[i].xrule <= im->end)
2886 gfx_new_line(im->canvas,
2887 xtr(im,im->gdes[i].xrule),im->yorigin,
2888 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2889 1.0,im->gdes[i].col);
2897 if (strcmp(im->graphfile,"-")==0) {
2898 fo = im->graphhandle ? im->graphhandle : stdout;
2899 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2900 /* Change translation mode for stdout to BINARY */
2901 _setmode( _fileno( fo ), O_BINARY );
2904 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2905 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2906 rrd_strerror(errno));
2910 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2911 if (strcmp(im->graphfile,"-") != 0)
2917 /*****************************************************
2919 *****************************************************/
2922 gdes_alloc(image_desc_t *im){
2925 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2926 * sizeof(graph_desc_t)))==NULL){
2927 rrd_set_error("realloc graph_descs");
2932 im->gdes[im->gdes_c-1].step=im->step;
2933 im->gdes[im->gdes_c-1].step_orig=im->step;
2934 im->gdes[im->gdes_c-1].stack=0;
2935 im->gdes[im->gdes_c-1].linewidth=0;
2936 im->gdes[im->gdes_c-1].debug=0;
2937 im->gdes[im->gdes_c-1].start=im->start;
2938 im->gdes[im->gdes_c-1].start_orig=im->start;
2939 im->gdes[im->gdes_c-1].end=im->end;
2940 im->gdes[im->gdes_c-1].end_orig=im->end;
2941 im->gdes[im->gdes_c-1].vname[0]='\0';
2942 im->gdes[im->gdes_c-1].data=NULL;
2943 im->gdes[im->gdes_c-1].ds_namv=NULL;
2944 im->gdes[im->gdes_c-1].data_first=0;
2945 im->gdes[im->gdes_c-1].p_data=NULL;
2946 im->gdes[im->gdes_c-1].rpnp=NULL;
2947 im->gdes[im->gdes_c-1].shift=0;
2948 im->gdes[im->gdes_c-1].col = 0x0;
2949 im->gdes[im->gdes_c-1].legend[0]='\0';
2950 im->gdes[im->gdes_c-1].format[0]='\0';
2951 im->gdes[im->gdes_c-1].strftm=0;
2952 im->gdes[im->gdes_c-1].rrd[0]='\0';
2953 im->gdes[im->gdes_c-1].ds=-1;
2954 im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;
2955 im->gdes[im->gdes_c-1].cf=CF_AVERAGE;
2956 im->gdes[im->gdes_c-1].p_data=NULL;
2957 im->gdes[im->gdes_c-1].yrule=DNAN;
2958 im->gdes[im->gdes_c-1].xrule=0;
2962 /* copies input untill the first unescaped colon is found
2963 or until input ends. backslashes have to be escaped as well */
2965 scan_for_col(const char *const input, int len, char *const output)
2970 input[inp] != ':' &&
2973 if (input[inp] == '\\' &&
2974 input[inp+1] != '\0' &&
2975 (input[inp+1] == '\\' ||
2976 input[inp+1] == ':')){
2977 output[outp++] = input[++inp];
2980 output[outp++] = input[inp];
2983 output[outp] = '\0';
2986 /* Some surgery done on this function, it became ridiculously big.
2988 ** - initializing now in rrd_graph_init()
2989 ** - options parsing now in rrd_graph_options()
2990 ** - script parsing now in rrd_graph_script()
2993 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
2996 rrd_graph_init(&im);
2997 im.graphhandle = stream;
2999 rrd_graph_options(argc,argv,&im);
3000 if (rrd_test_error()) {
3005 if (strlen(argv[optind])>=MAXPATH) {
3006 rrd_set_error("filename (including path) too long");
3010 strncpy(im.graphfile,argv[optind],MAXPATH-1);
3011 im.graphfile[MAXPATH-1]='\0';
3013 rrd_graph_script(argc,argv,&im,1);
3014 if (rrd_test_error()) {
3019 /* Everything is now read and the actual work can start */
3022 if (graph_paint(&im,prdata)==-1){
3027 /* The image is generated and needs to be output.
3028 ** Also, if needed, print a line with information about the image.
3038 /* maybe prdata is not allocated yet ... lets do it now */
3039 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
3040 rrd_set_error("malloc imginfo");
3044 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3046 rrd_set_error("malloc imginfo");
3049 filename=im.graphfile+strlen(im.graphfile);
3050 while(filename > im.graphfile) {
3051 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3055 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
3062 rrd_graph_init(image_desc_t *im)
3069 #ifdef HAVE_SETLOCALE
3070 setlocale(LC_TIME,"");
3071 #ifdef HAVE_MBSTOWCS
3072 setlocale(LC_CTYPE,"");
3078 im->xlab_user.minsec = -1;
3084 im->ylegend[0] = '\0';
3085 im->title[0] = '\0';
3086 im->watermark[0] = '\0';
3089 im->unitsexponent= 9999;
3092 im->viewfactor = 1.0;
3099 im->logarithmic = 0;
3100 im->ygridstep = DNAN;
3101 im->draw_x_grid = 1;
3102 im->draw_y_grid = 1;
3107 im->canvas = gfx_new_canvas();
3108 im->grid_dash_on = 1;
3109 im->grid_dash_off = 1;
3110 im->tabwidth = 40.0;
3112 for(i=0;i<DIM(graph_col);i++)
3113 im->graph_col[i]=graph_col[i];
3115 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3118 char rrd_win_default_font[1000];
3119 windir = getenv("windir");
3120 /* %windir% is something like D:\windows or C:\winnt */
3121 if (windir != NULL) {
3122 strncpy(rrd_win_default_font,windir,500);
3123 rrd_win_default_font[500] = '\0';
3124 strcat(rrd_win_default_font,"\\fonts\\");
3125 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
3126 for(i=0;i<DIM(text_prop);i++){
3127 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3128 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3135 deffont = getenv("RRD_DEFAULT_FONT");
3136 if (deffont != NULL) {
3137 for(i=0;i<DIM(text_prop);i++){
3138 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3139 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3143 for(i=0;i<DIM(text_prop);i++){
3144 im->text_prop[i].size = text_prop[i].size;
3145 strcpy(im->text_prop[i].font,text_prop[i].font);
3150 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3153 char *parsetime_error = NULL;
3154 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3155 time_t start_tmp=0,end_tmp=0;
3157 struct rrd_time_value start_tv, end_tv;
3159 optind = 0; opterr = 0; /* initialize getopt */
3161 parsetime("end-24h", &start_tv);
3162 parsetime("now", &end_tv);
3164 /* defines for long options without a short equivalent. should be bytes,
3165 and may not collide with (the ASCII value of) short options */
3166 #define LONGOPT_UNITS_SI 255
3169 static struct option long_options[] =
3171 {"start", required_argument, 0, 's'},
3172 {"end", required_argument, 0, 'e'},
3173 {"x-grid", required_argument, 0, 'x'},
3174 {"y-grid", required_argument, 0, 'y'},
3175 {"vertical-label",required_argument,0,'v'},
3176 {"width", required_argument, 0, 'w'},
3177 {"height", required_argument, 0, 'h'},
3178 {"interlaced", no_argument, 0, 'i'},
3179 {"upper-limit",required_argument, 0, 'u'},
3180 {"lower-limit",required_argument, 0, 'l'},
3181 {"rigid", no_argument, 0, 'r'},
3182 {"base", required_argument, 0, 'b'},
3183 {"logarithmic",no_argument, 0, 'o'},
3184 {"color", required_argument, 0, 'c'},
3185 {"font", required_argument, 0, 'n'},
3186 {"title", required_argument, 0, 't'},
3187 {"imginfo", required_argument, 0, 'f'},
3188 {"imgformat", required_argument, 0, 'a'},
3189 {"lazy", no_argument, 0, 'z'},
3190 {"zoom", required_argument, 0, 'm'},
3191 {"no-legend", no_argument, 0, 'g'},
3192 {"force-rules-legend",no_argument,0, 'F'},
3193 {"only-graph", no_argument, 0, 'j'},
3194 {"alt-y-grid", no_argument, 0, 'Y'},
3195 {"no-minor", no_argument, 0, 'I'},
3196 {"slope-mode", no_argument, 0, 'E'},
3197 {"alt-autoscale", no_argument, 0, 'A'},
3198 {"alt-autoscale-max", no_argument, 0, 'M'},
3199 {"no-gridfit", no_argument, 0, 'N'},
3200 {"units-exponent",required_argument, 0, 'X'},
3201 {"units-length",required_argument, 0, 'L'},
3202 {"units", required_argument, 0, LONGOPT_UNITS_SI },
3203 {"step", required_argument, 0, 'S'},
3204 {"tabwidth", required_argument, 0, 'T'},
3205 {"font-render-mode", required_argument, 0, 'R'},
3206 {"font-smoothing-threshold", required_argument, 0, 'B'},
3207 {"watermark", required_argument, 0, 'W'},
3208 {"alt-y-mrtg", no_argument, 0, 1000}, /* this has no effect it is just here to save old apps from crashing when they use it */
3210 int option_index = 0;
3212 int col_start,col_end;
3214 opt = getopt_long(argc, argv,
3215 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3216 long_options, &option_index);
3223 im->extra_flags |= NOMINOR;
3226 im->extra_flags |= ALTYGRID;
3229 im->extra_flags |= ALTAUTOSCALE;
3232 im->extra_flags |= ALTAUTOSCALE_MAX;
3235 im->extra_flags |= ONLY_GRAPH;
3238 im->extra_flags |= NOLEGEND;
3241 im->extra_flags |= FORCE_RULES_LEGEND;
3243 case LONGOPT_UNITS_SI:
3244 if(im->extra_flags & FORCE_UNITS) {
3245 rrd_set_error("--units can only be used once!");
3248 if(strcmp(optarg,"si")==0)
3249 im->extra_flags |= FORCE_UNITS_SI;
3251 rrd_set_error("invalid argument for --units: %s", optarg );
3256 im->unitsexponent = atoi(optarg);
3259 im->unitslength = atoi(optarg);
3262 im->tabwidth = atof(optarg);
3265 im->step = atoi(optarg);
3271 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3272 rrd_set_error( "start time: %s", parsetime_error );
3277 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3278 rrd_set_error( "end time: %s", parsetime_error );
3283 if(strcmp(optarg,"none") == 0){
3289 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3291 &im->xlab_user.gridst,
3293 &im->xlab_user.mgridst,
3295 &im->xlab_user.labst,
3296 &im->xlab_user.precis,
3297 &stroff) == 7 && stroff != 0){
3298 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3299 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3300 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3301 rrd_set_error("unknown keyword %s",scan_gtm);
3303 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3304 rrd_set_error("unknown keyword %s",scan_mtm);
3306 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3307 rrd_set_error("unknown keyword %s",scan_ltm);
3310 im->xlab_user.minsec = 1;
3311 im->xlab_user.stst = im->xlab_form;
3313 rrd_set_error("invalid x-grid format");
3319 if(strcmp(optarg,"none") == 0){
3327 &im->ylabfact) == 2) {
3328 if(im->ygridstep<=0){
3329 rrd_set_error("grid step must be > 0");
3331 } else if (im->ylabfact < 1){
3332 rrd_set_error("label factor must be > 0");
3336 rrd_set_error("invalid y-grid format");
3341 strncpy(im->ylegend,optarg,150);
3342 im->ylegend[150]='\0';
3345 im->maxval = atof(optarg);
3348 im->minval = atof(optarg);
3351 im->base = atol(optarg);
3352 if(im->base != 1024 && im->base != 1000 ){
3353 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3358 long_tmp = atol(optarg);
3359 if (long_tmp < 10) {
3360 rrd_set_error("width below 10 pixels");
3363 im->xsize = long_tmp;
3366 long_tmp = atol(optarg);
3367 if (long_tmp < 10) {
3368 rrd_set_error("height below 10 pixels");
3371 im->ysize = long_tmp;
3374 im->canvas->interlaced = 1;
3380 im->imginfo = optarg;
3383 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3384 rrd_set_error("unsupported graphics format '%s'",optarg);
3396 im->logarithmic = 1;
3400 "%10[A-Z]#%n%8lx%n",
3401 col_nam,&col_start,&color,&col_end) == 2){
3403 int col_len = col_end - col_start;
3407 ((color & 0xF00) * 0x110000) |
3408 ((color & 0x0F0) * 0x011000) |
3409 ((color & 0x00F) * 0x001100) |
3415 ((color & 0xF000) * 0x11000) |
3416 ((color & 0x0F00) * 0x01100) |
3417 ((color & 0x00F0) * 0x00110) |
3418 ((color & 0x000F) * 0x00011)
3422 color = (color << 8) + 0xff /* shift left by 8 */;
3427 rrd_set_error("the color format is #RRGGBB[AA]");
3430 if((ci=grc_conv(col_nam)) != -1){
3431 im->graph_col[ci]=color;
3433 rrd_set_error("invalid color name '%s'",col_nam);
3437 rrd_set_error("invalid color def format");
3444 char font[1024] = "";
3447 "%10[A-Z]:%lf:%1000s",
3448 prop,&size,font) >= 2){
3450 if((sindex=text_prop_conv(prop)) != -1){
3451 for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){
3453 im->text_prop[propidx].size=size;
3455 if (strlen(font) > 0){
3456 strcpy(im->text_prop[propidx].font,font);
3458 if (propidx==sindex && sindex != 0) break;
3461 rrd_set_error("invalid fonttag '%s'",prop);
3465 rrd_set_error("invalid text property format");
3471 im->canvas->zoom = atof(optarg);
3472 if (im->canvas->zoom <= 0.0) {
3473 rrd_set_error("zoom factor must be > 0");
3478 strncpy(im->title,optarg,150);
3479 im->title[150]='\0';
3483 if ( strcmp( optarg, "normal" ) == 0 )
3484 im->canvas->aa_type = AA_NORMAL;
3485 else if ( strcmp( optarg, "light" ) == 0 )
3486 im->canvas->aa_type = AA_LIGHT;
3487 else if ( strcmp( optarg, "mono" ) == 0 )
3488 im->canvas->aa_type = AA_NONE;
3491 rrd_set_error("unknown font-render-mode '%s'", optarg );
3497 im->canvas->font_aa_threshold = atof(optarg);
3501 strncpy(im->watermark,optarg,100);
3502 im->watermark[99]='\0';
3507 rrd_set_error("unknown option '%c'", optopt);
3509 rrd_set_error("unknown option '%s'",argv[optind-1]);
3514 if (optind >= argc) {
3515 rrd_set_error("missing filename");
3519 if (im->logarithmic == 1 && im->minval <= 0){
3520 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3524 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3525 /* error string is set in parsetime.c */
3529 if (start_tmp < 3600*24*365*10){
3530 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3534 if (end_tmp < start_tmp) {
3535 rrd_set_error("start (%ld) should be less than end (%ld)",
3536 start_tmp, end_tmp);
3540 im->start = start_tmp;
3542 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3546 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3548 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3549 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3555 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3558 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3560 color=strstr(var,"#");
3563 rrd_set_error("Found no color in %s",err);
3572 rest=strstr(color,":");
3580 sscanf(color,"#%6lx%n",&col,&n);
3581 col = (col << 8) + 0xff /* shift left by 8 */;
3582 if (n!=7) rrd_set_error("Color problem in %s",err);
3585 sscanf(color,"#%8lx%n",&col,&n);
3588 rrd_set_error("Color problem in %s",err);
3590 if (rrd_test_error()) return 0;
3597 int bad_format(char *fmt) {
3601 while (*ptr != '\0')
3602 if (*ptr++ == '%') {
3604 /* line cannot end with percent char */
3605 if (*ptr == '\0') return 1;
3607 /* '%s', '%S' and '%%' are allowed */
3608 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3610 /* %c is allowed (but use only with vdef!) */
3611 else if (*ptr == 'c') {
3616 /* or else '% 6.2lf' and such are allowed */
3618 /* optional padding character */
3619 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3621 /* This should take care of 'm.n' with all three optional */
3622 while (*ptr >= '0' && *ptr <= '9') ptr++;
3623 if (*ptr == '.') ptr++;
3624 while (*ptr >= '0' && *ptr <= '9') ptr++;
3626 /* Either 'le', 'lf' or 'lg' must follow here */
3627 if (*ptr++ != 'l') return 1;
3628 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3639 vdef_parse(gdes,str)
3640 struct graph_desc_t *gdes;
3641 const char *const str;
3643 /* A VDEF currently is either "func" or "param,func"
3644 * so the parsing is rather simple. Change if needed.
3651 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3652 if (n== (int)strlen(str)) { /* matched */
3656 sscanf(str,"%29[A-Z]%n",func,&n);
3657 if (n== (int)strlen(str)) { /* matched */
3660 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3667 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3668 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3669 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3670 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3671 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3672 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3673 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3674 else if (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3675 else if (!strcmp("LSLINT", func)) gdes->vf.op = VDEF_LSLINT;
3676 else if (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3678 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3685 switch (gdes->vf.op) {
3687 if (isnan(param)) { /* no parameter given */
3688 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3694 if (param>=0.0 && param<=100.0) {
3695 gdes->vf.param = param;
3696 gdes->vf.val = DNAN; /* undefined */
3697 gdes->vf.when = 0; /* undefined */
3699 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3714 case VDEF_LSLCORREL:
3716 gdes->vf.param = DNAN;
3717 gdes->vf.val = DNAN;
3720 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3737 graph_desc_t *src,*dst;
3741 dst = &im->gdes[gdi];
3742 src = &im->gdes[dst->vidx];
3743 data = src->data + src->ds;
3744 steps = (src->end - src->start) / src->step;
3747 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3754 switch (dst->vf.op) {
3755 case VDEF_PERCENT: {
3756 rrd_value_t * array;
3760 if ((array = malloc(steps*sizeof(double)))==NULL) {
3761 rrd_set_error("malloc VDEV_PERCENT");
3764 for (step=0;step < steps; step++) {
3765 array[step]=data[step*src->ds_cnt];
3767 qsort(array,step,sizeof(double),vdef_percent_compar);
3769 field = (steps-1)*dst->vf.param/100;
3770 dst->vf.val = array[field];
3771 dst->vf.when = 0; /* no time component */
3774 for(step=0;step<steps;step++)
3775 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3781 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3782 if (step == steps) {
3786 dst->vf.val = data[step*src->ds_cnt];
3787 dst->vf.when = src->start + (step+1)*src->step;
3789 while (step != steps) {
3790 if (finite(data[step*src->ds_cnt])) {
3791 if (data[step*src->ds_cnt] > dst->vf.val) {
3792 dst->vf.val = data[step*src->ds_cnt];
3793 dst->vf.when = src->start + (step+1)*src->step;
3800 case VDEF_AVERAGE: {
3803 for (step=0;step<steps;step++) {
3804 if (finite(data[step*src->ds_cnt])) {
3805 sum += data[step*src->ds_cnt];
3810 if (dst->vf.op == VDEF_TOTAL) {
3811 dst->vf.val = sum*src->step;
3812 dst->vf.when = 0; /* no time component */
3814 dst->vf.val = sum/cnt;
3815 dst->vf.when = 0; /* no time component */
3825 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3826 if (step == steps) {
3830 dst->vf.val = data[step*src->ds_cnt];
3831 dst->vf.when = src->start + (step+1)*src->step;
3833 while (step != steps) {
3834 if (finite(data[step*src->ds_cnt])) {
3835 if (data[step*src->ds_cnt] < dst->vf.val) {
3836 dst->vf.val = data[step*src->ds_cnt];
3837 dst->vf.when = src->start + (step+1)*src->step;
3844 /* The time value returned here is one step before the
3845 * actual time value. This is the start of the first
3849 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3850 if (step == steps) { /* all entries were NaN */
3854 dst->vf.val = data[step*src->ds_cnt];
3855 dst->vf.when = src->start + step*src->step;
3859 /* The time value returned here is the
3860 * actual time value. This is the end of the last
3864 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3865 if (step < 0) { /* all entries were NaN */
3869 dst->vf.val = data[step*src->ds_cnt];
3870 dst->vf.when = src->start + (step+1)*src->step;
3875 case VDEF_LSLCORREL:{
3876 /* Bestfit line by linear least squares method */
3879 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3880 SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3882 for (step=0;step<steps;step++) {
3883 if (finite(data[step*src->ds_cnt])) {
3886 SUMxx += step * step;
3887 SUMxy += step * data[step*src->ds_cnt];
3888 SUMy += data[step*src->ds_cnt];
3889 SUMyy += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3893 slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3894 y_intercept = ( SUMy - slope*SUMx ) / cnt;
3895 correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3898 if (dst->vf.op == VDEF_LSLSLOPE) {
3899 dst->vf.val = slope;
3901 } else if (dst->vf.op == VDEF_LSLINT) {
3902 dst->vf.val = y_intercept;
3904 } else if (dst->vf.op == VDEF_LSLCORREL) {
3905 dst->vf.val = correl;
3919 /* NaN < -INF < finite_values < INF */
3921 vdef_percent_compar(a,b)
3924 /* Equality is not returned; this doesn't hurt except
3925 * (maybe) for a little performance.
3928 /* First catch NaN values. They are smallest */
3929 if (isnan( *(double *)a )) return -1;
3930 if (isnan( *(double *)b )) return 1;
3932 /* NaN doesn't reach this part so INF and -INF are extremes.
3933 * The sign from isinf() is compatible with the sign we return
3935 if (isinf( *(double *)a )) return isinf( *(double *)a );
3936 if (isinf( *(double *)b )) return isinf( *(double *)b );
3938 /* If we reach this, both values must be finite */
3939 if ( *(double *)a < *(double *)b ) return -1; else return 1;