1 /****************************************************************************
2 * RRDtool 1.2.13 Copyright by Tobi Oetiker, 1997-2006
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, 0, TMT_SECOND,30, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
46 {2, 0, TMT_MINUTE,1, TMT_MINUTE,5, TMT_MINUTE,5, 0,"%H:%M"},
47 {5, 0, TMT_MINUTE,2, TMT_MINUTE,10, TMT_MINUTE,10, 0,"%H:%M"},
48 {10, 0, TMT_MINUTE,5, TMT_MINUTE,20, TMT_MINUTE,20, 0,"%H:%M"},
49 {30, 0, TMT_MINUTE,10, TMT_HOUR,1, TMT_HOUR,1, 0,"%H:%M"},
50 {60, 0, TMT_MINUTE,30, TMT_HOUR,2, TMT_HOUR,2, 0,"%H:%M"},
51 {180, 0, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%H:%M"},
52 {180, 1*24*3600, TMT_HOUR,1, TMT_HOUR,6, TMT_HOUR,6, 0,"%a %H:%M"},
53 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly*/
54 {600, 0, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a"},
55 {600, 1*24*3600, TMT_HOUR,6, TMT_DAY,1, TMT_DAY,1, 24*3600,"%a %d"},
56 {1800, 0, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a"},
57 {1800, 1*24*3600, TMT_HOUR,12, TMT_DAY,1, TMT_DAY,2, 24*3600,"%a %d"},
58 {3600, 0, TMT_DAY,1, TMT_WEEK,1, TMT_WEEK,1, 7*24*3600,"Week %V"},
59 {3*3600, 0, TMT_WEEK,1, TMT_MONTH,1, TMT_WEEK,2, 7*24*3600,"Week %V"},
60 {6*3600, 0, TMT_MONTH,1, TMT_MONTH,1, TMT_MONTH,1, 30*24*3600,"%b"},
61 {48*3600, 0, TMT_MONTH,1, TMT_MONTH,3, TMT_MONTH,3, 30*24*3600,"%b"},
62 {10*24*3600, 0, TMT_YEAR,1, TMT_YEAR,1, TMT_YEAR,1, 365*24*3600,"%y"},
63 {-1,0,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
66 /* sensible y label intervals ...*/
84 gfx_color_t graph_col[] = /* default colors */
85 { 0xFFFFFFFF, /* canvas */
86 0xF0F0F0FF, /* background */
87 0xD0D0D0FF, /* shade A */
88 0xA0A0A0FF, /* shade B */
89 0x90909080, /* grid */
90 0xE0505080, /* major grid */
91 0x000000FF, /* font */
92 0x802020FF, /* arrow */
93 0x202020FF, /* axis */
94 0x000000FF /* frame */
101 # define DPRINT(x) (void)(printf x, printf("\n"))
107 /* initialize with xtr(im,0); */
109 xtr(image_desc_t *im,time_t mytime){
112 pixie = (double) im->xsize / (double)(im->end - im->start);
115 return (int)((double)im->xorigin
116 + pixie * ( mytime - im->start ) );
119 /* translate data values into y coordinates */
121 ytr(image_desc_t *im, double value){
126 pixie = (double) im->ysize / (im->maxval - im->minval);
128 pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
130 } else if(!im->logarithmic) {
131 yval = im->yorigin - pixie * (value - im->minval);
133 if (value < im->minval) {
136 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
139 /* make sure we don't return anything too unreasonable. GD lib can
140 get terribly slow when drawing lines outside its scope. This is
141 especially problematic in connection with the rigid option */
143 /* keep yval as-is */
144 } else if (yval > im->yorigin) {
145 yval = im->yorigin +0.00001;
146 } else if (yval < im->yorigin - im->ysize){
147 yval = im->yorigin - im->ysize - 0.00001;
154 /* conversion function for symbolic entry names */
157 #define conv_if(VV,VVV) \
158 if (strcmp(#VV, string) == 0) return VVV ;
160 enum gf_en gf_conv(char *string){
162 conv_if(PRINT,GF_PRINT)
163 conv_if(GPRINT,GF_GPRINT)
164 conv_if(COMMENT,GF_COMMENT)
165 conv_if(HRULE,GF_HRULE)
166 conv_if(VRULE,GF_VRULE)
167 conv_if(LINE,GF_LINE)
168 conv_if(AREA,GF_AREA)
169 conv_if(STACK,GF_STACK)
170 conv_if(TICK,GF_TICK)
172 conv_if(CDEF,GF_CDEF)
173 conv_if(VDEF,GF_VDEF)
175 conv_if(PART,GF_PART)
177 conv_if(XPORT,GF_XPORT)
178 conv_if(SHIFT,GF_SHIFT)
183 enum gfx_if_en if_conv(char *string){
193 enum tmt_en tmt_conv(char *string){
195 conv_if(SECOND,TMT_SECOND)
196 conv_if(MINUTE,TMT_MINUTE)
197 conv_if(HOUR,TMT_HOUR)
199 conv_if(WEEK,TMT_WEEK)
200 conv_if(MONTH,TMT_MONTH)
201 conv_if(YEAR,TMT_YEAR)
205 enum grc_en grc_conv(char *string){
207 conv_if(BACK,GRC_BACK)
208 conv_if(CANVAS,GRC_CANVAS)
209 conv_if(SHADEA,GRC_SHADEA)
210 conv_if(SHADEB,GRC_SHADEB)
211 conv_if(GRID,GRC_GRID)
212 conv_if(MGRID,GRC_MGRID)
213 conv_if(FONT,GRC_FONT)
214 conv_if(ARROW,GRC_ARROW)
215 conv_if(AXIS,GRC_AXIS)
216 conv_if(FRAME,GRC_FRAME)
221 enum text_prop_en text_prop_conv(char *string){
223 conv_if(DEFAULT,TEXT_PROP_DEFAULT)
224 conv_if(TITLE,TEXT_PROP_TITLE)
225 conv_if(AXIS,TEXT_PROP_AXIS)
226 conv_if(UNIT,TEXT_PROP_UNIT)
227 conv_if(LEGEND,TEXT_PROP_LEGEND)
235 im_free(image_desc_t *im)
239 if (im == NULL) return 0;
240 for(i=0;i<(unsigned)im->gdes_c;i++){
241 if (im->gdes[i].data_first){
242 /* careful here, because a single pointer can occur several times */
243 free (im->gdes[i].data);
244 if (im->gdes[i].ds_namv){
245 for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
246 free(im->gdes[i].ds_namv[ii]);
247 free(im->gdes[i].ds_namv);
250 free (im->gdes[i].p_data);
251 free (im->gdes[i].rpnp);
254 gfx_destroy(im->canvas);
258 /* find SI magnitude symbol for the given number*/
261 image_desc_t *im, /* image description */
268 char *symbol[] = {"a", /* 10e-18 Atto */
269 "f", /* 10e-15 Femto */
270 "p", /* 10e-12 Pico */
271 "n", /* 10e-9 Nano */
272 "u", /* 10e-6 Micro */
273 "m", /* 10e-3 Milli */
278 "T", /* 10e12 Tera */
279 "P", /* 10e15 Peta */
285 if (*value == 0.0 || isnan(*value) ) {
289 sindex = floor(log(fabs(*value))/log((double)im->base));
290 *magfact = pow((double)im->base, (double)sindex);
291 (*value) /= (*magfact);
293 if ( sindex <= symbcenter && sindex >= -symbcenter) {
294 (*symb_ptr) = symbol[sindex+symbcenter];
302 static char si_symbol[] = {
303 'a', /* 10e-18 Atto */
304 'f', /* 10e-15 Femto */
305 'p', /* 10e-12 Pico */
306 'n', /* 10e-9 Nano */
307 'u', /* 10e-6 Micro */
308 'm', /* 10e-3 Milli */
313 'T', /* 10e12 Tera */
314 'P', /* 10e15 Peta */
317 static const int si_symbcenter = 6;
319 /* find SI magnitude symbol for the numbers on the y-axis*/
322 image_desc_t *im /* image description */
326 double digits,viewdigits=0;
328 digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base));
330 if (im->unitsexponent != 9999) {
331 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
332 viewdigits = floor(im->unitsexponent / 3);
337 im->magfact = pow((double)im->base , digits);
340 printf("digits %6.3f im->magfact %6.3f\n",digits,im->magfact);
343 im->viewfactor = im->magfact / pow((double)im->base , viewdigits);
345 if ( ((viewdigits+si_symbcenter) < sizeof(si_symbol)) &&
346 ((viewdigits+si_symbcenter) >= 0) )
347 im->symbol = si_symbol[(int)viewdigits+si_symbcenter];
352 /* move min and max values around to become sensible */
355 expand_range(image_desc_t *im)
357 double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
358 600.0,500.0,400.0,300.0,250.0,
359 200.0,125.0,100.0,90.0,80.0,
360 75.0,70.0,60.0,50.0,40.0,30.0,
361 25.0,20.0,10.0,9.0,8.0,
362 7.0,6.0,5.0,4.0,3.5,3.0,
363 2.5,2.0,1.8,1.5,1.2,1.0,
364 0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
366 double scaled_min,scaled_max;
373 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
374 im->minval,im->maxval,im->magfact);
377 if (isnan(im->ygridstep)){
378 if(im->extra_flags & ALTAUTOSCALE) {
379 /* measure the amplitude of the function. Make sure that
380 graph boundaries are slightly higher then max/min vals
381 so we can see amplitude on the graph */
384 delt = im->maxval - im->minval;
386 fact = 2.0 * pow(10.0,
387 floor(log10(max(fabs(im->minval), fabs(im->maxval))/im->magfact)) - 2);
389 adj = (fact - delt) * 0.55;
391 printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
397 else if(im->extra_flags & ALTAUTOSCALE_MAX) {
398 /* measure the amplitude of the function. Make sure that
399 graph boundaries are slightly higher than max vals
400 so we can see amplitude on the graph */
401 adj = (im->maxval - im->minval) * 0.1;
405 scaled_min = im->minval / im->magfact;
406 scaled_max = im->maxval / im->magfact;
408 for (i=1; sensiblevalues[i] > 0; i++){
409 if (sensiblevalues[i-1]>=scaled_min &&
410 sensiblevalues[i]<=scaled_min)
411 im->minval = sensiblevalues[i]*(im->magfact);
413 if (-sensiblevalues[i-1]<=scaled_min &&
414 -sensiblevalues[i]>=scaled_min)
415 im->minval = -sensiblevalues[i-1]*(im->magfact);
417 if (sensiblevalues[i-1] >= scaled_max &&
418 sensiblevalues[i] <= scaled_max)
419 im->maxval = sensiblevalues[i-1]*(im->magfact);
421 if (-sensiblevalues[i-1]<=scaled_max &&
422 -sensiblevalues[i] >=scaled_max)
423 im->maxval = -sensiblevalues[i]*(im->magfact);
427 /* adjust min and max to the grid definition if there is one */
428 im->minval = (double)im->ylabfact * im->ygridstep *
429 floor(im->minval / ((double)im->ylabfact * im->ygridstep));
430 im->maxval = (double)im->ylabfact * im->ygridstep *
431 ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
435 fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
436 im->minval,im->maxval,im->magfact);
441 apply_gridfit(image_desc_t *im)
443 if (isnan(im->minval) || isnan(im->maxval))
446 if (im->logarithmic) {
447 double ya, yb, ypix, ypixfrac;
448 double log10_range = log10(im->maxval) - log10(im->minval);
449 ya = pow((double)10, floor(log10(im->minval)));
450 while (ya < im->minval)
453 return; /* don't have y=10^x gridline */
455 if (yb <= im->maxval) {
456 /* we have at least 2 y=10^x gridlines.
457 Make sure distance between them in pixels
458 are an integer by expanding im->maxval */
459 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
460 double factor = y_pixel_delta / floor(y_pixel_delta);
461 double new_log10_range = factor * log10_range;
462 double new_ymax_log10 = log10(im->minval) + new_log10_range;
463 im->maxval = pow(10, new_ymax_log10);
464 ytr(im,DNAN); /* reset precalc */
465 log10_range = log10(im->maxval) - log10(im->minval);
467 /* make sure first y=10^x gridline is located on
468 integer pixel position by moving scale slightly
469 downwards (sub-pixel movement) */
470 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
471 ypixfrac = ypix - floor(ypix);
472 if (ypixfrac > 0 && ypixfrac < 1) {
473 double yfrac = ypixfrac / im->ysize;
474 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
475 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
476 ytr(im,DNAN); /* reset precalc */
479 /* Make sure we have an integer pixel distance between
480 each minor gridline */
481 double ypos1 = ytr(im, im->minval);
482 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
483 double y_pixel_delta = ypos1 - ypos2;
484 double factor = y_pixel_delta / floor(y_pixel_delta);
485 double new_range = factor * (im->maxval - im->minval);
486 double gridstep = im->ygrid_scale.gridstep;
487 double minor_y, minor_y_px, minor_y_px_frac;
488 im->maxval = im->minval + new_range;
489 ytr(im,DNAN); /* reset precalc */
490 /* make sure first minor gridline is on integer pixel y coord */
491 minor_y = gridstep * floor(im->minval / gridstep);
492 while (minor_y < im->minval)
494 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
495 minor_y_px_frac = minor_y_px - floor(minor_y_px);
496 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
497 double yfrac = minor_y_px_frac / im->ysize;
498 double range = im->maxval - im->minval;
499 im->minval = im->minval - yfrac * range;
500 im->maxval = im->maxval - yfrac * range;
501 ytr(im,DNAN); /* reset precalc */
503 calc_horizontal_grid(im); /* recalc with changed im->maxval */
507 /* reduce data reimplementation by Alex */
511 enum cf_en cf, /* which consolidation function ?*/
512 unsigned long cur_step, /* step the data currently is in */
513 time_t *start, /* start, end and step as requested ... */
514 time_t *end, /* ... by the application will be ... */
515 unsigned long *step, /* ... adjusted to represent reality */
516 unsigned long *ds_cnt, /* number of data sources in file */
517 rrd_value_t **data) /* two dimensional array containing the data */
519 int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
520 unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
521 rrd_value_t *srcptr,*dstptr;
523 (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
526 row_cnt = ((*end)-(*start))/cur_step;
532 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
533 row_cnt,reduce_factor,*start,*end,cur_step);
534 for (col=0;col<row_cnt;col++) {
535 printf("time %10lu: ",*start+(col+1)*cur_step);
536 for (i=0;i<*ds_cnt;i++)
537 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
542 /* We have to combine [reduce_factor] rows of the source
543 ** into one row for the destination. Doing this we also
544 ** need to take care to combine the correct rows. First
545 ** alter the start and end time so that they are multiples
546 ** of the new step time. We cannot reduce the amount of
547 ** time so we have to move the end towards the future and
548 ** the start towards the past.
550 end_offset = (*end) % (*step);
551 start_offset = (*start) % (*step);
553 /* If there is a start offset (which cannot be more than
554 ** one destination row), skip the appropriate number of
555 ** source rows and one destination row. The appropriate
556 ** number is what we do know (start_offset/cur_step) of
557 ** the new interval (*step/cur_step aka reduce_factor).
560 printf("start_offset: %lu end_offset: %lu\n",start_offset,end_offset);
561 printf("row_cnt before: %lu\n",row_cnt);
564 (*start) = (*start)-start_offset;
565 skiprows=reduce_factor-start_offset/cur_step;
566 srcptr+=skiprows* *ds_cnt;
567 for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
571 printf("row_cnt between: %lu\n",row_cnt);
574 /* At the end we have some rows that are not going to be
575 ** used, the amount is end_offset/cur_step
578 (*end) = (*end)-end_offset+(*step);
579 skiprows = end_offset/cur_step;
583 printf("row_cnt after: %lu\n",row_cnt);
586 /* Sanity check: row_cnt should be multiple of reduce_factor */
587 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
589 if (row_cnt%reduce_factor) {
590 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
591 row_cnt,reduce_factor);
592 printf("BUG in reduce_data()\n");
596 /* Now combine reduce_factor intervals at a time
597 ** into one interval for the destination.
600 for (dst_row=0;(long int)row_cnt>=reduce_factor;dst_row++) {
601 for (col=0;col<(*ds_cnt);col++) {
602 rrd_value_t newval=DNAN;
603 unsigned long validval=0;
605 for (i=0;i<reduce_factor;i++) {
606 if (isnan(srcptr[i*(*ds_cnt)+col])) {
610 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
618 newval += srcptr[i*(*ds_cnt)+col];
621 newval = min (newval,srcptr[i*(*ds_cnt)+col]);
624 /* an interval contains a failure if any subintervals contained a failure */
626 newval = max (newval,srcptr[i*(*ds_cnt)+col]);
629 newval = srcptr[i*(*ds_cnt)+col];
634 if (validval == 0){newval = DNAN;} else{
652 srcptr+=(*ds_cnt)*reduce_factor;
653 row_cnt-=reduce_factor;
655 /* If we had to alter the endtime, we didn't have enough
656 ** source rows to fill the last row. Fill it with NaN.
658 if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
660 row_cnt = ((*end)-(*start))/ *step;
662 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
663 row_cnt,*start,*end,*step);
664 for (col=0;col<row_cnt;col++) {
665 printf("time %10lu: ",*start+(col+1)*(*step));
666 for (i=0;i<*ds_cnt;i++)
667 printf(" %8.2e",srcptr[*ds_cnt*col+i]);
674 /* get the data required for the graphs from the
678 data_fetch(image_desc_t *im )
683 /* pull the data from the rrd files ... */
684 for (i=0;i< (int)im->gdes_c;i++){
685 /* only GF_DEF elements fetch data */
686 if (im->gdes[i].gf != GF_DEF)
690 /* do we have it already ?*/
691 for (ii=0;ii<i;ii++) {
692 if (im->gdes[ii].gf != GF_DEF)
694 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
695 && (im->gdes[i].cf == im->gdes[ii].cf)
696 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
697 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
698 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
699 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
700 /* OK, the data is already there.
701 ** Just copy the header portion
703 im->gdes[i].start = im->gdes[ii].start;
704 im->gdes[i].end = im->gdes[ii].end;
705 im->gdes[i].step = im->gdes[ii].step;
706 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
707 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
708 im->gdes[i].data = im->gdes[ii].data;
709 im->gdes[i].data_first = 0;
716 unsigned long ft_step = im->gdes[i].step ;
718 if((rrd_fetch_fn(im->gdes[i].rrd,
724 &im->gdes[i].ds_namv,
725 &im->gdes[i].data)) == -1){
728 im->gdes[i].data_first = 1;
729 im->gdes[i].step = im->step;
731 if (ft_step < im->gdes[i].step) {
732 reduce_data(im->gdes[i].cf_reduce,
740 im->gdes[i].step = ft_step;
744 /* lets see if the required data source is really there */
745 for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
746 if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
749 if (im->gdes[i].ds== -1){
750 rrd_set_error("No DS called '%s' in '%s'",
751 im->gdes[i].ds_nam,im->gdes[i].rrd);
759 /* evaluate the expressions in the CDEF functions */
761 /*************************************************************
763 *************************************************************/
766 find_var_wrapper(void *arg1, char *key)
768 return find_var((image_desc_t *) arg1, key);
771 /* find gdes containing var*/
773 find_var(image_desc_t *im, char *key){
775 for(ii=0;ii<im->gdes_c-1;ii++){
776 if((im->gdes[ii].gf == GF_DEF
777 || im->gdes[ii].gf == GF_VDEF
778 || im->gdes[ii].gf == GF_CDEF)
779 && (strcmp(im->gdes[ii].vname,key) == 0)){
786 /* find the largest common denominator for all the numbers
787 in the 0 terminated num array */
792 for (i=0;num[i+1]!=0;i++){
794 rest=num[i] % num[i+1];
795 num[i]=num[i+1]; num[i+1]=rest;
799 /* return i==0?num[i]:num[i-1]; */
803 /* run the rpn calculator on all the VDEF and CDEF arguments */
805 data_calc( image_desc_t *im){
809 long *steparray, rpi;
814 rpnstack_init(&rpnstack);
816 for (gdi=0;gdi<im->gdes_c;gdi++){
817 /* Look for GF_VDEF and GF_CDEF in the same loop,
818 * so CDEFs can use VDEFs and vice versa
820 switch (im->gdes[gdi].gf) {
824 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
826 /* remove current shift */
827 vdp->start -= vdp->shift;
828 vdp->end -= vdp->shift;
831 if (im->gdes[gdi].shidx >= 0)
832 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
835 vdp->shift = im->gdes[gdi].shval;
837 /* normalize shift to multiple of consolidated step */
838 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
841 vdp->start += vdp->shift;
842 vdp->end += vdp->shift;
846 /* A VDEF has no DS. This also signals other parts
847 * of rrdtool that this is a VDEF value, not a CDEF.
849 im->gdes[gdi].ds_cnt = 0;
850 if (vdef_calc(im,gdi)) {
851 rrd_set_error("Error processing VDEF '%s'"
854 rpnstack_free(&rpnstack);
859 im->gdes[gdi].ds_cnt = 1;
860 im->gdes[gdi].ds = 0;
861 im->gdes[gdi].data_first = 1;
862 im->gdes[gdi].start = 0;
863 im->gdes[gdi].end = 0;
868 /* Find the variables in the expression.
869 * - VDEF variables are substituted by their values
870 * and the opcode is changed into OP_NUMBER.
871 * - CDEF variables are analized for their step size,
872 * the lowest common denominator of all the step
873 * sizes of the data sources involved is calculated
874 * and the resulting number is the step size for the
875 * resulting data source.
877 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
878 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
879 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
880 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
881 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
883 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
885 im->gdes[ptr].vname);
886 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
888 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
889 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
890 } else { /* normal variables and PREF(variables) */
892 /* add one entry to the array that keeps track of the step sizes of the
893 * data sources going into the CDEF. */
895 rrd_realloc(steparray,
896 (++stepcnt+1)*sizeof(*steparray)))==NULL){
897 rrd_set_error("realloc steparray");
898 rpnstack_free(&rpnstack);
902 steparray[stepcnt-1] = im->gdes[ptr].step;
904 /* adjust start and end of cdef (gdi) so
905 * that it runs from the latest start point
906 * to the earliest endpoint of any of the
907 * rras involved (ptr)
910 if(im->gdes[gdi].start < im->gdes[ptr].start)
911 im->gdes[gdi].start = im->gdes[ptr].start;
913 if(im->gdes[gdi].end == 0 ||
914 im->gdes[gdi].end > im->gdes[ptr].end)
915 im->gdes[gdi].end = im->gdes[ptr].end;
917 /* store pointer to the first element of
918 * the rra providing data for variable,
919 * further save step size and data source
922 im->gdes[gdi].rpnp[rpi].data = im->gdes[ptr].data + im->gdes[ptr].ds;
923 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
924 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
926 /* backoff the *.data ptr; this is done so
927 * rpncalc() function doesn't have to treat
928 * the first case differently
930 } /* if ds_cnt != 0 */
931 } /* if OP_VARIABLE */
932 } /* loop through all rpi */
934 /* move the data pointers to the correct period */
935 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
936 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
937 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
938 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
939 long diff = im->gdes[gdi].start - im->gdes[ptr].start;
942 im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
946 if(steparray == NULL){
947 rrd_set_error("rpn expressions without DEF"
948 " or CDEF variables are not supported");
949 rpnstack_free(&rpnstack);
952 steparray[stepcnt]=0;
953 /* Now find the resulting step. All steps in all
954 * used RRAs have to be visited
956 im->gdes[gdi].step = lcd(steparray);
958 if((im->gdes[gdi].data = malloc((
959 (im->gdes[gdi].end-im->gdes[gdi].start)
960 / im->gdes[gdi].step)
961 * sizeof(double)))==NULL){
962 rrd_set_error("malloc im->gdes[gdi].data");
963 rpnstack_free(&rpnstack);
967 /* Step through the new cdef results array and
968 * calculate the values
970 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
971 now<=im->gdes[gdi].end;
972 now += im->gdes[gdi].step)
974 rpnp_t *rpnp = im -> gdes[gdi].rpnp;
976 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
977 * in this case we are advancing by timesteps;
978 * we use the fact that time_t is a synonym for long
980 if (rpn_calc(rpnp,&rpnstack,(long) now,
981 im->gdes[gdi].data,++dataidx) == -1) {
982 /* rpn_calc sets the error string */
983 rpnstack_free(&rpnstack);
986 } /* enumerate over time steps within a CDEF */
991 } /* enumerate over CDEFs */
992 rpnstack_free(&rpnstack);
996 /* massage data so, that we get one value for each x coordinate in the graph */
998 data_proc( image_desc_t *im ){
1000 double pixstep = (double)(im->end-im->start)
1001 /(double)im->xsize; /* how much time
1002 passes in one pixel */
1004 double minval=DNAN,maxval=DNAN;
1006 unsigned long gr_time;
1008 /* memory for the processed data */
1009 for(i=0;i<im->gdes_c;i++) {
1010 if((im->gdes[i].gf==GF_LINE) ||
1011 (im->gdes[i].gf==GF_AREA) ||
1012 (im->gdes[i].gf==GF_TICK)) {
1013 if((im->gdes[i].p_data = malloc((im->xsize +1)
1014 * sizeof(rrd_value_t)))==NULL){
1015 rrd_set_error("malloc data_proc");
1021 for (i=0;i<im->xsize;i++) { /* for each pixel */
1023 gr_time = im->start+pixstep*i; /* time of the current step */
1026 for (ii=0;ii<im->gdes_c;ii++) {
1028 switch (im->gdes[ii].gf) {
1032 if (!im->gdes[ii].stack)
1034 value = im->gdes[ii].yrule;
1035 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1036 /* The time of the data doesn't necessarily match
1037 ** the time of the graph. Beware.
1039 vidx = im->gdes[ii].vidx;
1040 if (im->gdes[vidx].gf == GF_VDEF) {
1041 value = im->gdes[vidx].vf.val;
1042 } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1043 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1044 value = im->gdes[vidx].data[
1045 (unsigned long) floor(
1046 (double)(gr_time - im->gdes[vidx].start)
1047 / im->gdes[vidx].step)
1048 * im->gdes[vidx].ds_cnt
1056 if (! isnan(value)) {
1058 im->gdes[ii].p_data[i] = paintval;
1059 /* GF_TICK: the data values are not
1060 ** relevant for min and max
1062 if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1063 if (isnan(minval) || paintval < minval)
1065 if (isnan(maxval) || paintval > maxval)
1069 im->gdes[ii].p_data[i] = DNAN;
1073 rrd_set_error("STACK should already be turned into LINE or AREA here");
1082 /* if min or max have not been asigned a value this is because
1083 there was no data in the graph ... this is not good ...
1084 lets set these to dummy values then ... */
1086 if (im->logarithmic) {
1087 if (isnan(minval)) minval = 0.2;
1088 if (isnan(maxval)) maxval = 5.1;
1091 if (isnan(minval)) minval = 0.0;
1092 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 */ /* why not? */
1098 || ((!im->rigid) && im->minval > minval)
1100 if (im->logarithmic)
1101 im->minval = minval * 0.5;
1103 im->minval = minval;
1105 if (isnan(im->maxval)
1106 || (!im->rigid && im->maxval < maxval)
1108 if (im->logarithmic)
1109 im->maxval = maxval * 2.0;
1111 im->maxval = maxval;
1113 /* make sure min is smaller than max */
1114 if (im->minval > im->maxval) {
1115 im->minval = 0.99 * im->maxval;
1118 /* make sure min and max are not equal */
1119 if (im->minval == im->maxval) {
1121 if (! im->logarithmic) {
1124 /* make sure min and max are not both zero */
1125 if (im->maxval == 0.0) {
1134 /* identify the point where the first gridline, label ... gets placed */
1138 time_t start, /* what is the initial time */
1139 enum tmt_en baseint, /* what is the basic interval */
1140 long basestep /* how many if these do we jump a time */
1144 localtime_r(&start, &tm);
1147 tm.tm_sec -= tm.tm_sec % basestep; break;
1150 tm.tm_min -= tm.tm_min % basestep;
1155 tm.tm_hour -= tm.tm_hour % basestep; break;
1157 /* we do NOT look at the basestep for this ... */
1160 tm.tm_hour = 0; break;
1162 /* we do NOT look at the basestep for this ... */
1166 tm.tm_mday -= tm.tm_wday -1; /* -1 because we want the monday */
1167 if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1174 tm.tm_mon -= tm.tm_mon % basestep; break;
1182 tm.tm_year -= (tm.tm_year+1900) % basestep;
1187 /* identify the point where the next gridline, label ... gets placed */
1190 time_t current, /* what is the initial time */
1191 enum tmt_en baseint, /* what is the basic interval */
1192 long basestep /* how many if these do we jump a time */
1197 localtime_r(¤t, &tm);
1201 tm.tm_sec += basestep; break;
1203 tm.tm_min += basestep; break;
1205 tm.tm_hour += basestep; break;
1207 tm.tm_mday += basestep; break;
1209 tm.tm_mday += 7*basestep; break;
1211 tm.tm_mon += basestep; break;
1213 tm.tm_year += basestep;
1215 madetime = mktime(&tm);
1216 } while (madetime == -1); /* this is necessary to skip impssible times
1217 like the daylight saving time skips */
1223 /* calculate values required for PRINT and GPRINT functions */
1226 print_calc(image_desc_t *im, char ***prdata)
1228 long i,ii,validsteps;
1231 int graphelement = 0;
1234 double magfact = -1;
1238 /* wow initializing tmvdef is quite a task :-) */
1239 time_t now = time(NULL);
1240 localtime_r(&now,&tmvdef);
1241 if (im->imginfo) prlines++;
1242 for(i=0;i<im->gdes_c;i++){
1243 switch(im->gdes[i].gf){
1246 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1247 rrd_set_error("realloc prdata");
1251 /* PRINT and GPRINT can now print VDEF generated values.
1252 * There's no need to do any calculations on them as these
1253 * calculations were already made.
1255 vidx = im->gdes[i].vidx;
1256 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1257 printval = im->gdes[vidx].vf.val;
1258 localtime_r(&im->gdes[vidx].vf.when,&tmvdef);
1259 } else { /* need to calculate max,min,avg etcetera */
1260 max_ii =((im->gdes[vidx].end
1261 - im->gdes[vidx].start)
1262 / im->gdes[vidx].step
1263 * im->gdes[vidx].ds_cnt);
1266 for( ii=im->gdes[vidx].ds;
1268 ii+=im->gdes[vidx].ds_cnt){
1269 if (! finite(im->gdes[vidx].data[ii]))
1271 if (isnan(printval)){
1272 printval = im->gdes[vidx].data[ii];
1277 switch (im->gdes[i].cf){
1280 case CF_DEVSEASONAL:
1284 printval += im->gdes[vidx].data[ii];
1287 printval = min( printval, im->gdes[vidx].data[ii]);
1291 printval = max( printval, im->gdes[vidx].data[ii]);
1294 printval = im->gdes[vidx].data[ii];
1297 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1298 if (validsteps > 1) {
1299 printval = (printval / validsteps);
1302 } /* prepare printval */
1304 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1305 /* Magfact is set to -1 upon entry to print_calc. If it
1306 * is still less than 0, then we need to run auto_scale.
1307 * Otherwise, put the value into the correct units. If
1308 * the value is 0, then do not set the symbol or magnification
1309 * so next the calculation will be performed again. */
1310 if (magfact < 0.0) {
1311 auto_scale(im,&printval,&si_symb,&magfact);
1312 if (printval == 0.0)
1315 printval /= magfact;
1317 *(++percent_s) = 's';
1318 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1319 auto_scale(im,&printval,&si_symb,&magfact);
1322 if (im->gdes[i].gf == GF_PRINT){
1323 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1324 (*prdata)[prlines-1] = NULL;
1325 if (im->gdes[i].strftm){
1326 strftime((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1328 if (bad_format(im->gdes[i].format)) {
1329 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1333 #ifdef HAVE_SNPRINTF
1334 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1336 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1342 if (im->gdes[i].strftm){
1343 strftime(im->gdes[i].legend,FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
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);
1369 #ifdef WITH_PIECHART
1376 rrd_set_error("STACK should already be turned into LINE or AREA here");
1381 return graphelement;
1385 /* place legends with color spots */
1387 leg_place(image_desc_t *im)
1390 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1391 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1392 int fill=0, fill_last;
1394 int leg_x = border, leg_y = im->yimg;
1395 int leg_y_prev = im->yimg;
1399 char prt_fctn; /*special printfunctions */
1402 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1403 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1404 rrd_set_error("malloc for legspace");
1408 for(i=0;i<im->gdes_c;i++){
1411 /* hid legends for rules which are not displayed */
1413 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1414 if (im->gdes[i].gf == GF_HRULE &&
1415 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1416 im->gdes[i].legend[0] = '\0';
1418 if (im->gdes[i].gf == GF_VRULE &&
1419 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1420 im->gdes[i].legend[0] = '\0';
1423 leg_cc = strlen(im->gdes[i].legend);
1425 /* is there a controle code ant the end of the legend string ? */
1426 /* and it is not a tab \\t */
1427 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1428 prt_fctn = im->gdes[i].legend[leg_cc-1];
1430 im->gdes[i].legend[leg_cc] = '\0';
1434 /* remove exess space */
1435 while (prt_fctn=='g' &&
1437 im->gdes[i].legend[leg_cc-1]==' '){
1439 im->gdes[i].legend[leg_cc]='\0';
1442 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1445 /* no interleg space if string ends in \g */
1446 fill += legspace[i];
1448 fill += gfx_get_text_width(im->canvas, fill+border,
1449 im->text_prop[TEXT_PROP_LEGEND].font,
1450 im->text_prop[TEXT_PROP_LEGEND].size,
1452 im->gdes[i].legend, 0);
1457 /* who said there was a special tag ... ?*/
1458 if (prt_fctn=='g') {
1461 if (prt_fctn == '\0') {
1462 if (i == im->gdes_c -1 ) prt_fctn ='l';
1464 /* is it time to place the legends ? */
1465 if (fill > im->ximg - 2*border){
1480 if (prt_fctn != '\0'){
1482 if (leg_c >= 2 && prt_fctn == 'j') {
1483 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1487 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1488 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1490 for(ii=mark;ii<=i;ii++){
1491 if(im->gdes[ii].legend[0]=='\0')
1492 continue; /* skip empty legends */
1493 im->gdes[ii].leg_x = leg_x;
1494 im->gdes[ii].leg_y = leg_y;
1496 gfx_get_text_width(im->canvas, leg_x,
1497 im->text_prop[TEXT_PROP_LEGEND].font,
1498 im->text_prop[TEXT_PROP_LEGEND].size,
1500 im->gdes[ii].legend, 0)
1505 /* only add y space if there was text on the line */
1506 if (leg_x > border || prt_fctn == 's')
1507 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1508 if (prt_fctn == 's')
1509 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1515 im->yimg = leg_y_prev;
1516 /* if we did place some legends we have to add vertical space */
1517 if (leg_y != im->yimg){
1518 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1525 /* create a grid on the graph. it determines what to do
1526 from the values of xsize, start and end */
1528 /* the xaxis labels are determined from the number of seconds per pixel
1529 in the requested graph */
1534 calc_horizontal_grid(image_desc_t *im)
1540 int decimals, fractionals;
1542 im->ygrid_scale.labfact=2;
1543 range = im->maxval - im->minval;
1544 scaledrange = range / im->magfact;
1546 /* does the scale of this graph make it impossible to put lines
1547 on it? If so, give up. */
1548 if (isnan(scaledrange)) {
1552 /* find grid spaceing */
1554 if(isnan(im->ygridstep)){
1555 if(im->extra_flags & ALTYGRID) {
1556 /* find the value with max number of digits. Get number of digits */
1557 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1558 if(decimals <= 0) /* everything is small. make place for zero */
1561 im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1563 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1564 im->ygrid_scale.gridstep = 0.1;
1565 /* should have at least 5 lines but no more then 15 */
1566 if(range/im->ygrid_scale.gridstep < 5)
1567 im->ygrid_scale.gridstep /= 10;
1568 if(range/im->ygrid_scale.gridstep > 15)
1569 im->ygrid_scale.gridstep *= 10;
1570 if(range/im->ygrid_scale.gridstep > 5) {
1571 im->ygrid_scale.labfact = 1;
1572 if(range/im->ygrid_scale.gridstep > 8)
1573 im->ygrid_scale.labfact = 2;
1576 im->ygrid_scale.gridstep /= 5;
1577 im->ygrid_scale.labfact = 5;
1579 fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1580 if(fractionals < 0) { /* small amplitude. */
1581 int len = decimals - fractionals + 1;
1582 if (im->unitslength < len+2) im->unitslength = len+2;
1583 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1585 int len = decimals + 1;
1586 if (im->unitslength < len+2) im->unitslength = len+2;
1587 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1591 for(i=0;ylab[i].grid > 0;i++){
1592 pixel = im->ysize / (scaledrange / ylab[i].grid);
1599 if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1600 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1605 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1608 im->ygrid_scale.gridstep = im->ygridstep;
1609 im->ygrid_scale.labfact = im->ylabfact;
1614 int draw_horizontal_grid(image_desc_t *im)
1618 char graph_label[100];
1620 double X0=im->xorigin;
1621 double X1=im->xorigin+im->xsize;
1623 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1624 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1626 scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1627 MaxY = scaledstep*(double)egrid;
1628 for (i = sgrid; i <= egrid; i++){
1629 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1630 double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
1631 if ( Y0 >= im->yorigin-im->ysize
1632 && Y0 <= im->yorigin){
1633 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1634 with the chosen settings. Add a label if required by settings, or if
1635 there is only one label so far and the next grid line is out of bounds. */
1636 if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){
1637 if (im->symbol == ' ') {
1638 if(im->extra_flags & ALTYGRID) {
1639 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1642 sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1644 sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1648 char sisym = ( i == 0 ? ' ' : im->symbol);
1649 if(im->extra_flags & ALTYGRID) {
1650 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1653 sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1655 sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1661 gfx_new_text ( im->canvas,
1662 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1663 im->graph_col[GRC_FONT],
1664 im->text_prop[TEXT_PROP_AXIS].font,
1665 im->text_prop[TEXT_PROP_AXIS].size,
1666 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1668 gfx_new_dashed_line ( im->canvas,
1671 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1672 im->grid_dash_on, im->grid_dash_off);
1674 } else if (!(im->extra_flags & NOMINOR)) {
1675 gfx_new_dashed_line ( im->canvas,
1678 GRIDWIDTH, im->graph_col[GRC_GRID],
1679 im->grid_dash_on, im->grid_dash_off);
1687 /* this is frexp for base 10 */
1688 double frexp10(double, double *);
1689 double frexp10(double x, double *e) {
1693 iexp = floor(log(fabs(x)) / log(10));
1694 mnt = x / pow(10.0, iexp);
1697 mnt = x / pow(10.0, iexp);
1703 /* logaritmic horizontal grid */
1705 horizontal_log_grid(image_desc_t *im)
1707 double yloglab[][10] = {
1708 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1709 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1710 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1711 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1712 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.}};
1714 int i, j, val_exp, min_exp;
1715 double nex; /* number of decades in data */
1716 double logscale; /* scale in logarithmic space */
1717 int exfrac = 1; /* decade spacing */
1718 int mid = -1; /* row in yloglab for major grid */
1719 double mspac; /* smallest major grid spacing (pixels) */
1720 int flab; /* first value in yloglab to use */
1723 char graph_label[100];
1725 nex = log10(im->maxval / im->minval);
1726 logscale = im->ysize / nex;
1728 /* major spacing for data with high dynamic range */
1729 while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1730 if(exfrac == 1) exfrac = 3;
1734 /* major spacing for less dynamic data */
1736 /* search best row in yloglab */
1738 for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
1739 mspac = logscale * log10(10.0 / yloglab[mid][i]);
1740 } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && mid < 5);
1743 /* find first value in yloglab */
1744 for(flab = 0; frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
1745 if(yloglab[mid][flab] == 10.0) {
1750 if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
1753 X1=im->xorigin+im->xsize;
1757 value = yloglab[mid][flab] * pow(10.0, val_exp);
1759 Y0 = ytr(im, value);
1760 if(Y0 <= im->yorigin - im->ysize) break;
1762 /* major grid line */
1763 gfx_new_dashed_line ( im->canvas,
1766 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1767 im->grid_dash_on, im->grid_dash_off);
1770 if (im->extra_flags & FORCE_UNITS_SI) {
1775 scale = floor(val_exp / 3.0);
1776 if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
1777 else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
1778 pvalue *= yloglab[mid][flab];
1780 if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
1781 ((scale+si_symbcenter) >= 0) )
1782 symbol = si_symbol[scale+si_symbcenter];
1786 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1788 sprintf(graph_label,"%3.0e", value);
1789 gfx_new_text ( im->canvas,
1790 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1791 im->graph_col[GRC_FONT],
1792 im->text_prop[TEXT_PROP_AXIS].font,
1793 im->text_prop[TEXT_PROP_AXIS].size,
1794 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1798 if(mid < 4 && exfrac == 1) {
1799 /* find first and last minor line behind current major line
1800 * i is the first line and j tha last */
1802 min_exp = val_exp - 1;
1803 for(i = 1; yloglab[mid][i] < 10.0; i++);
1804 i = yloglab[mid][i - 1] + 1;
1809 i = yloglab[mid][flab - 1] + 1;
1810 j = yloglab[mid][flab];
1813 /* draw minor lines below current major line */
1816 value = i * pow(10.0, min_exp);
1817 if(value < im->minval) continue;
1819 Y0 = ytr(im, value);
1820 if(Y0 <= im->yorigin - im->ysize) break;
1823 gfx_new_dashed_line ( im->canvas,
1826 GRIDWIDTH, im->graph_col[GRC_GRID],
1827 im->grid_dash_on, im->grid_dash_off);
1830 else if(exfrac > 1) {
1831 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1832 value = pow(10.0, i);
1833 if(value < im->minval) continue;
1835 Y0 = ytr(im, value);
1836 if(Y0 <= im->yorigin - im->ysize) break;
1839 gfx_new_dashed_line ( im->canvas,
1842 GRIDWIDTH, im->graph_col[GRC_GRID],
1843 im->grid_dash_on, im->grid_dash_off);
1848 if(yloglab[mid][++flab] == 10.0) {
1854 /* draw minor lines after highest major line */
1855 if(mid < 4 && exfrac == 1) {
1856 /* find first and last minor line below current major line
1857 * i is the first line and j tha last */
1859 min_exp = val_exp - 1;
1860 for(i = 1; yloglab[mid][i] < 10.0; i++);
1861 i = yloglab[mid][i - 1] + 1;
1866 i = yloglab[mid][flab - 1] + 1;
1867 j = yloglab[mid][flab];
1870 /* draw minor lines below current major line */
1873 value = i * pow(10.0, min_exp);
1874 if(value < im->minval) continue;
1876 Y0 = ytr(im, value);
1877 if(Y0 <= im->yorigin - im->ysize) break;
1880 gfx_new_dashed_line ( im->canvas,
1883 GRIDWIDTH, im->graph_col[GRC_GRID],
1884 im->grid_dash_on, im->grid_dash_off);
1887 /* fancy minor gridlines */
1888 else if(exfrac > 1) {
1889 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1890 value = pow(10.0, i);
1891 if(value < im->minval) continue;
1893 Y0 = ytr(im, value);
1894 if(Y0 <= im->yorigin - im->ysize) break;
1897 gfx_new_dashed_line ( im->canvas,
1900 GRIDWIDTH, im->graph_col[GRC_GRID],
1901 im->grid_dash_on, im->grid_dash_off);
1913 int xlab_sel; /* which sort of label and grid ? */
1914 time_t ti, tilab, timajor;
1916 char graph_label[100];
1917 double X0,Y0,Y1; /* points for filled graph and more*/
1920 /* the type of time grid is determined by finding
1921 the number of seconds per pixel in the graph */
1924 if(im->xlab_user.minsec == -1){
1925 factor=(im->end - im->start)/im->xsize;
1927 while ( xlab[xlab_sel+1].minsec != -1
1928 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; } /* pick the last one */
1929 while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
1930 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; } /* go back to the smallest size */
1931 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1932 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1933 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1934 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1935 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1936 im->xlab_user.labst = xlab[xlab_sel].labst;
1937 im->xlab_user.precis = xlab[xlab_sel].precis;
1938 im->xlab_user.stst = xlab[xlab_sel].stst;
1941 /* y coords are the same for every line ... */
1943 Y1 = im->yorigin-im->ysize;
1946 /* paint the minor grid */
1947 if (!(im->extra_flags & NOMINOR))
1949 for(ti = find_first_time(im->start,
1950 im->xlab_user.gridtm,
1951 im->xlab_user.gridst),
1952 timajor = find_first_time(im->start,
1953 im->xlab_user.mgridtm,
1954 im->xlab_user.mgridst);
1956 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1958 /* are we inside the graph ? */
1959 if (ti < im->start || ti > im->end) continue;
1960 while (timajor < ti) {
1961 timajor = find_next_time(timajor,
1962 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1964 if (ti == timajor) continue; /* skip as falls on major grid line */
1966 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1967 im->graph_col[GRC_GRID],
1968 im->grid_dash_on, im->grid_dash_off);
1973 /* paint the major grid */
1974 for(ti = find_first_time(im->start,
1975 im->xlab_user.mgridtm,
1976 im->xlab_user.mgridst);
1978 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1980 /* are we inside the graph ? */
1981 if (ti < im->start || ti > im->end) continue;
1983 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1984 im->graph_col[GRC_MGRID],
1985 im->grid_dash_on, im->grid_dash_off);
1988 /* paint the labels below the graph */
1989 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
1990 im->xlab_user.labtm,
1991 im->xlab_user.labst);
1992 ti <= im->end - im->xlab_user.precis/2;
1993 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1995 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1996 /* are we inside the graph ? */
1997 if (tilab < im->start || tilab > im->end) continue;
2000 localtime_r(&tilab, &tm);
2001 strftime(graph_label,99,im->xlab_user.stst, &tm);
2003 # error "your libc has no strftime I guess we'll abort the exercise here."
2005 gfx_new_text ( im->canvas,
2006 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
2007 im->graph_col[GRC_FONT],
2008 im->text_prop[TEXT_PROP_AXIS].font,
2009 im->text_prop[TEXT_PROP_AXIS].size,
2010 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
2023 /* draw x and y axis */
2024 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2025 im->xorigin+im->xsize,im->yorigin-im->ysize,
2026 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2028 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2029 im->xorigin+im->xsize,im->yorigin-im->ysize,
2030 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2032 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
2033 im->xorigin+im->xsize+4,im->yorigin,
2034 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2036 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
2037 im->xorigin,im->yorigin-im->ysize-4,
2038 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2041 /* arrow for X and Y axis direction */
2042 gfx_new_area ( im->canvas,
2043 im->xorigin+im->xsize+2, im->yorigin-2,
2044 im->xorigin+im->xsize+2, im->yorigin+3,
2045 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
2046 im->graph_col[GRC_ARROW]);
2048 gfx_new_area ( im->canvas,
2049 im->xorigin-2, im->yorigin-im->ysize-2,
2050 im->xorigin+3, im->yorigin-im->ysize-2,
2051 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
2052 im->graph_col[GRC_ARROW]);
2057 grid_paint(image_desc_t *im)
2061 double X0,Y0; /* points for filled graph and more*/
2064 /* draw 3d border */
2065 node = gfx_new_area (im->canvas, 0,im->yimg,
2067 2,2,im->graph_col[GRC_SHADEA]);
2068 gfx_add_point( node , im->ximg - 2, 2 );
2069 gfx_add_point( node , im->ximg, 0 );
2070 gfx_add_point( node , 0,0 );
2071 /* gfx_add_point( node , 0,im->yimg ); */
2073 node = gfx_new_area (im->canvas, 2,im->yimg-2,
2074 im->ximg-2,im->yimg-2,
2076 im->graph_col[GRC_SHADEB]);
2077 gfx_add_point( node , im->ximg,0);
2078 gfx_add_point( node , im->ximg,im->yimg);
2079 gfx_add_point( node , 0,im->yimg);
2080 /* gfx_add_point( node , 0,im->yimg ); */
2083 if (im->draw_x_grid == 1 )
2086 if (im->draw_y_grid == 1){
2087 if(im->logarithmic){
2088 res = horizontal_log_grid(im);
2090 res = draw_horizontal_grid(im);
2093 /* dont draw horizontal grid if there is no min and max val */
2095 char *nodata = "No Data found";
2096 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
2097 im->graph_col[GRC_FONT],
2098 im->text_prop[TEXT_PROP_AXIS].font,
2099 im->text_prop[TEXT_PROP_AXIS].size,
2100 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
2105 /* yaxis unit description */
2106 gfx_new_text( im->canvas,
2107 10, (im->yorigin - im->ysize/2),
2108 im->graph_col[GRC_FONT],
2109 im->text_prop[TEXT_PROP_UNIT].font,
2110 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2111 RRDGRAPH_YLEGEND_ANGLE,
2112 GFX_H_LEFT, GFX_V_CENTER,
2116 gfx_new_text( im->canvas,
2117 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2118 im->graph_col[GRC_FONT],
2119 im->text_prop[TEXT_PROP_TITLE].font,
2120 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2121 GFX_H_CENTER, GFX_V_CENTER,
2123 /* rrdtool 'logo' */
2124 gfx_new_text( im->canvas,
2126 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2127 im->text_prop[TEXT_PROP_AXIS].font,
2128 5.5, im->tabwidth, 270,
2129 GFX_H_RIGHT, GFX_V_TOP,
2130 "RRDTOOL / TOBI OETIKER");
2132 /* graph watermark */
2133 if(im->watermark[0] != '\0') {
2134 gfx_new_text( im->canvas,
2135 im->ximg/2, im->yimg-6,
2136 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2137 im->text_prop[TEXT_PROP_AXIS].font,
2138 5.5, im->tabwidth, 0,
2139 GFX_H_CENTER, GFX_V_BOTTOM,
2144 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2145 for(i=0;i<im->gdes_c;i++){
2146 if(im->gdes[i].legend[0] =='\0')
2149 /* im->gdes[i].leg_y is the bottom of the legend */
2150 X0 = im->gdes[i].leg_x;
2151 Y0 = im->gdes[i].leg_y;
2152 gfx_new_text ( im->canvas, X0, Y0,
2153 im->graph_col[GRC_FONT],
2154 im->text_prop[TEXT_PROP_LEGEND].font,
2155 im->text_prop[TEXT_PROP_LEGEND].size,
2156 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2157 im->gdes[i].legend );
2158 /* The legend for GRAPH items starts with "M " to have
2159 enough space for the box */
2160 if ( im->gdes[i].gf != GF_PRINT &&
2161 im->gdes[i].gf != GF_GPRINT &&
2162 im->gdes[i].gf != GF_COMMENT) {
2165 boxH = gfx_get_text_width(im->canvas, 0,
2166 im->text_prop[TEXT_PROP_LEGEND].font,
2167 im->text_prop[TEXT_PROP_LEGEND].size,
2168 im->tabwidth,"o", 0) * 1.2;
2171 /* make sure transparent colors show up the same way as in the graph */
2172 node = gfx_new_area(im->canvas,
2176 im->graph_col[GRC_BACK]);
2177 gfx_add_point ( node, X0+boxH, Y0-boxV );
2179 node = gfx_new_area(im->canvas,
2184 gfx_add_point ( node, X0+boxH, Y0-boxV );
2185 node = gfx_new_line(im->canvas,
2188 1.0,im->graph_col[GRC_FRAME]);
2189 gfx_add_point(node,X0+boxH,Y0);
2190 gfx_add_point(node,X0+boxH,Y0-boxV);
2191 gfx_close_path(node);
2198 /*****************************************************
2199 * lazy check make sure we rely need to create this graph
2200 *****************************************************/
2202 int lazy_check(image_desc_t *im){
2205 struct stat imgstat;
2207 if (im->lazy == 0) return 0; /* no lazy option */
2208 if (stat(im->graphfile,&imgstat) != 0)
2209 return 0; /* can't stat */
2210 /* one pixel in the existing graph is more then what we would
2212 if (time(NULL) - imgstat.st_mtime >
2213 (im->end - im->start) / im->xsize)
2215 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2216 return 0; /* the file does not exist */
2217 switch (im->canvas->imgformat) {
2219 size = PngSize(fd,&(im->ximg),&(im->yimg));
2228 #ifdef WITH_PIECHART
2230 pie_part(image_desc_t *im, gfx_color_t color,
2231 double PieCenterX, double PieCenterY, double Radius,
2232 double startangle, double endangle)
2236 double step=M_PI/50; /* Number of iterations for the circle;
2237 ** 10 is definitely too low, more than
2238 ** 50 seems to be overkill
2241 /* Strange but true: we have to work clockwise or else
2242 ** anti aliasing nor transparency don't work.
2244 ** This test is here to make sure we do it right, also
2245 ** this makes the for...next loop more easy to implement.
2246 ** The return will occur if the user enters a negative number
2247 ** (which shouldn't be done according to the specs) or if the
2248 ** programmers do something wrong (which, as we all know, never
2249 ** happens anyway :)
2251 if (endangle<startangle) return;
2253 /* Hidden feature: Radius decreases each full circle */
2255 while (angle>=2*M_PI) {
2260 node=gfx_new_area(im->canvas,
2261 PieCenterX+sin(startangle)*Radius,
2262 PieCenterY-cos(startangle)*Radius,
2265 PieCenterX+sin(endangle)*Radius,
2266 PieCenterY-cos(endangle)*Radius,
2268 for (angle=endangle;angle-startangle>=step;angle-=step) {
2270 PieCenterX+sin(angle)*Radius,
2271 PieCenterY-cos(angle)*Radius );
2278 graph_size_location(image_desc_t *im, int elements
2280 #ifdef WITH_PIECHART
2286 /* The actual size of the image to draw is determined from
2287 ** several sources. The size given on the command line is
2288 ** the graph area but we need more as we have to draw labels
2289 ** and other things outside the graph area
2292 /* +-+-------------------------------------------+
2293 ** |l|.................title.....................|
2294 ** |e+--+-------------------------------+--------+
2297 ** |l| l| main graph area | chart |
2300 ** |r+--+-------------------------------+--------+
2301 ** |e| | x-axis labels | |
2302 ** |v+--+-------------------------------+--------+
2303 ** | |..............legends......................|
2304 ** +-+-------------------------------------------+
2306 ** +---------------------------------------------+
2312 #ifdef WITH_PIECHART
2317 Xlegend =0, Ylegend =0,
2319 Xspacing =15, Yspacing =15,
2323 if (im->extra_flags & ONLY_GRAPH) {
2325 im->ximg = im->xsize;
2326 im->yimg = im->ysize;
2327 im->yorigin = im->ysize;
2332 if (im->ylegend[0] != '\0' ) {
2333 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2337 if (im->title[0] != '\0') {
2338 /* The title is placed "inbetween" two text lines so it
2339 ** automatically has some vertical spacing. The horizontal
2340 ** spacing is added here, on each side.
2342 /* don't care for the with of the title
2343 Xtitle = gfx_get_text_width(im->canvas, 0,
2344 im->text_prop[TEXT_PROP_TITLE].font,
2345 im->text_prop[TEXT_PROP_TITLE].size,
2347 im->title, 0) + 2*Xspacing; */
2348 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2354 if (im->draw_x_grid) {
2355 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2357 if (im->draw_y_grid) {
2358 Xylabel=gfx_get_text_width(im->canvas, 0,
2359 im->text_prop[TEXT_PROP_AXIS].font,
2360 im->text_prop[TEXT_PROP_AXIS].size,
2362 "0", 0) * im->unitslength;
2366 #ifdef WITH_PIECHART
2368 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2374 /* Now calculate the total size. Insert some spacing where
2375 desired. im->xorigin and im->yorigin need to correspond
2376 with the lower left corner of the main graph area or, if
2377 this one is not set, the imaginary box surrounding the
2380 /* The legend width cannot yet be determined, as a result we
2381 ** have problems adjusting the image to it. For now, we just
2382 ** forget about it at all; the legend will have to fit in the
2383 ** size already allocated.
2385 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2387 #ifdef WITH_PIECHART
2391 if (Xmain) im->ximg += Xspacing;
2392 #ifdef WITH_PIECHART
2393 if (Xpie) im->ximg += Xspacing;
2396 im->xorigin = Xspacing + Xylabel;
2398 /* the length of the title should not influence with width of the graph
2399 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2401 if (Xvertical) { /* unit description */
2402 im->ximg += Xvertical;
2403 im->xorigin += Xvertical;
2407 /* The vertical size is interesting... we need to compare
2408 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2409 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2410 ** in order to start even thinking about Ylegend or Ywatermark.
2412 ** Do it in three portions: First calculate the inner part,
2413 ** then do the legend, then adjust the total height of the img,
2414 ** adding space for a watermark if one exists;
2417 /* reserve space for main and/or pie */
2419 im->yimg = Ymain + Yxlabel;
2421 #ifdef WITH_PIECHART
2422 if (im->yimg < Ypie) im->yimg = Ypie;
2425 im->yorigin = im->yimg - Yxlabel;
2427 /* reserve space for the title *or* some padding above the graph */
2430 im->yorigin += Ytitle;
2432 im->yimg += 1.5*Yspacing;
2433 im->yorigin += 1.5*Yspacing;
2435 /* reserve space for padding below the graph */
2436 im->yimg += Yspacing;
2438 /* Determine where to place the legends onto the image.
2439 ** Adjust im->yimg to match the space requirements.
2441 if(leg_place(im)==-1)
2444 if (im->watermark[0] != '\0') {
2445 im->yimg += Ywatermark;
2449 if (Xlegend > im->ximg) {
2451 /* reposition Pie */
2455 #ifdef WITH_PIECHART
2456 /* The pie is placed in the upper right hand corner,
2457 ** just below the title (if any) and with sufficient
2461 im->pie_x = im->ximg - Xspacing - Xpie/2;
2462 im->pie_y = im->yorigin-Ymain+Ypie/2;
2464 im->pie_x = im->ximg/2;
2465 im->pie_y = im->yorigin-Ypie/2;
2473 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2474 /* yes we are loosing precision by doing tos with floats instead of doubles
2475 but it seems more stable this way. */
2477 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
2480 int aInt = *(int*)&A;
2481 int bInt = *(int*)&B;
2483 /* Make sure maxUlps is non-negative and small enough that the
2484 default NAN won't compare as equal to anything. */
2486 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
2488 /* Make aInt lexicographically ordered as a twos-complement int */
2491 aInt = 0x80000000l - aInt;
2493 /* Make bInt lexicographically ordered as a twos-complement int */
2496 bInt = 0x80000000l - bInt;
2498 intDiff = abs(aInt - bInt);
2500 if (intDiff <= maxUlps)
2506 /* draw that picture thing ... */
2508 graph_paint(image_desc_t *im, char ***calcpr)
2511 int lazy = lazy_check(im);
2512 #ifdef WITH_PIECHART
2514 double PieStart=0.0;
2519 double areazero = 0.0;
2520 graph_desc_t *lastgdes = NULL;
2522 /* if we are lazy and there is nothing to PRINT ... quit now */
2523 if (lazy && im->prt_c==0) return 0;
2525 /* pull the data from the rrd files ... */
2527 if(data_fetch(im)==-1)
2530 /* evaluate VDEF and CDEF operations ... */
2531 if(data_calc(im)==-1)
2534 #ifdef WITH_PIECHART
2535 /* check if we need to draw a piechart */
2536 for(i=0;i<im->gdes_c;i++){
2537 if (im->gdes[i].gf == GF_PART) {
2544 /* calculate and PRINT and GPRINT definitions. We have to do it at
2545 * this point because it will affect the length of the legends
2546 * if there are no graph elements we stop here ...
2547 * if we are lazy, try to quit ...
2549 i=print_calc(im,calcpr);
2552 #ifdef WITH_PIECHART
2555 ) || lazy) return 0;
2557 #ifdef WITH_PIECHART
2558 /* If there's only the pie chart to draw, signal this */
2559 if (i==0) piechart=2;
2562 /* get actual drawing data and find min and max values*/
2563 if(data_proc(im)==-1)
2566 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2568 if(!im->rigid && ! im->logarithmic)
2569 expand_range(im); /* make sure the upper and lower limit are
2572 if (!calc_horizontal_grid(im))
2579 /**************************************************************
2580 *** Calculating sizes and locations became a bit confusing ***
2581 *** so I moved this into a separate function. ***
2582 **************************************************************/
2583 if(graph_size_location(im,i
2584 #ifdef WITH_PIECHART
2590 /* the actual graph is created by going through the individual
2591 graph elements and then drawing them */
2593 node=gfx_new_area ( im->canvas,
2597 im->graph_col[GRC_BACK]);
2599 gfx_add_point(node,im->ximg, 0);
2601 #ifdef WITH_PIECHART
2602 if (piechart != 2) {
2604 node=gfx_new_area ( im->canvas,
2605 im->xorigin, im->yorigin,
2606 im->xorigin + im->xsize, im->yorigin,
2607 im->xorigin + im->xsize, im->yorigin-im->ysize,
2608 im->graph_col[GRC_CANVAS]);
2610 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2612 if (im->minval > 0.0)
2613 areazero = im->minval;
2614 if (im->maxval < 0.0)
2615 areazero = im->maxval;
2616 #ifdef WITH_PIECHART
2620 #ifdef WITH_PIECHART
2622 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2626 for(i=0;i<im->gdes_c;i++){
2627 switch(im->gdes[i].gf){
2640 for (ii = 0; ii < im->xsize; ii++)
2642 if (!isnan(im->gdes[i].p_data[ii]) &&
2643 im->gdes[i].p_data[ii] != 0.0)
2645 if (im -> gdes[i].yrule > 0 ) {
2646 gfx_new_line(im->canvas,
2647 im -> xorigin + ii, im->yorigin,
2648 im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2650 im -> gdes[i].col );
2651 } else if ( im -> gdes[i].yrule < 0 ) {
2652 gfx_new_line(im->canvas,
2653 im -> xorigin + ii, im->yorigin - im -> ysize,
2654 im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2656 im -> gdes[i].col );
2664 /* fix data points at oo and -oo */
2665 for(ii=0;ii<im->xsize;ii++){
2666 if (isinf(im->gdes[i].p_data[ii])){
2667 if (im->gdes[i].p_data[ii] > 0) {
2668 im->gdes[i].p_data[ii] = im->maxval ;
2670 im->gdes[i].p_data[ii] = im->minval ;
2676 /* *******************************************************
2681 -------|--t-1--t--------------------------------
2683 if we know the value at time t was a then
2684 we draw a square from t-1 to t with the value a.
2686 ********************************************************* */
2687 if (im->gdes[i].col != 0x0){
2688 /* GF_LINE and friend */
2689 if(im->gdes[i].gf == GF_LINE ){
2692 for(ii=1;ii<im->xsize;ii++){
2693 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2697 if ( node == NULL ) {
2698 last_y = ytr(im,im->gdes[i].p_data[ii]);
2699 if ( im->slopemode == 0 ){
2700 node = gfx_new_line(im->canvas,
2701 ii-1+im->xorigin,last_y,
2702 ii+im->xorigin,last_y,
2703 im->gdes[i].linewidth,
2706 node = gfx_new_line(im->canvas,
2707 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2708 ii+im->xorigin,last_y,
2709 im->gdes[i].linewidth,
2713 double new_y = ytr(im,im->gdes[i].p_data[ii]);
2714 if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2715 gfx_add_point(node,ii-1+im->xorigin,new_y);
2718 gfx_add_point(node,ii+im->xorigin,new_y);
2724 double *foreY=malloc(sizeof(double)*im->xsize*2);
2725 double *foreX=malloc(sizeof(double)*im->xsize*2);
2726 double *backY=malloc(sizeof(double)*im->xsize*2);
2727 double *backX=malloc(sizeof(double)*im->xsize*2);
2729 for(ii=0;ii<=im->xsize;ii++){
2731 if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2734 while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2735 node = gfx_new_area(im->canvas,
2738 foreX[cntI],foreY[cntI], im->gdes[i].col);
2739 while (cntI < idxI) {
2742 while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2743 gfx_add_point(node,foreX[cntI],foreY[cntI]);
2745 gfx_add_point(node,backX[idxI],backY[idxI]);
2749 while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;}
2750 gfx_add_point(node,backX[idxI],backY[idxI]);
2759 if (ii == im->xsize) break;
2761 /* keep things simple for now, just draw these bars
2762 do not try to build a big and complex area */
2765 if ( im->slopemode == 0 && ii==0){
2768 if ( isnan(im->gdes[i].p_data[ii]) ) {
2772 ytop = ytr(im,im->gdes[i].p_data[ii]);
2773 if ( lastgdes && im->gdes[i].stack ) {
2774 ybase = ytr(im,lastgdes->p_data[ii]);
2776 ybase = ytr(im,areazero);
2778 if ( ybase == ytop ){
2782 /* every area has to be wound clock-wise,
2783 so we have to make sur base remains base */
2785 double extra = ytop;
2789 if ( im->slopemode == 0 ){
2790 backY[++idxI] = ybase-0.2;
2791 backX[idxI] = ii+im->xorigin-1;
2792 foreY[idxI] = ytop+0.2;
2793 foreX[idxI] = ii+im->xorigin-1;
2795 backY[++idxI] = ybase-0.2;
2796 backX[idxI] = ii+im->xorigin;
2797 foreY[idxI] = ytop+0.2;
2798 foreX[idxI] = ii+im->xorigin;
2800 /* close up any remaining area */
2805 } /* else GF_LINE */
2806 } /* if color != 0x0 */
2807 /* make sure we do not run into trouble when stacking on NaN */
2808 for(ii=0;ii<im->xsize;ii++){
2809 if (isnan(im->gdes[i].p_data[ii])) {
2810 if (lastgdes && (im->gdes[i].stack)) {
2811 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2813 im->gdes[i].p_data[ii] = areazero;
2817 lastgdes = &(im->gdes[i]);
2819 #ifdef WITH_PIECHART
2821 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2822 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2824 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2825 pie_part(im,im->gdes[i].col,
2826 im->pie_x,im->pie_y,im->piesize*0.4,
2827 M_PI*2.0*PieStart/100.0,
2828 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2829 PieStart += im->gdes[i].yrule;
2834 rrd_set_error("STACK should already be turned into LINE or AREA here");
2840 #ifdef WITH_PIECHART
2848 /* grid_paint also does the text */
2849 if( !(im->extra_flags & ONLY_GRAPH) )
2853 if( !(im->extra_flags & ONLY_GRAPH) )
2856 /* the RULES are the last thing to paint ... */
2857 for(i=0;i<im->gdes_c;i++){
2859 switch(im->gdes[i].gf){
2861 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2862 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2864 if(im->gdes[i].yrule >= im->minval
2865 && im->gdes[i].yrule <= im->maxval)
2866 gfx_new_line(im->canvas,
2867 im->xorigin,ytr(im,im->gdes[i].yrule),
2868 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2869 1.0,im->gdes[i].col);
2872 if(im->gdes[i].xrule == 0) { /* fetch variable */
2873 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2875 if(im->gdes[i].xrule >= im->start
2876 && im->gdes[i].xrule <= im->end)
2877 gfx_new_line(im->canvas,
2878 xtr(im,im->gdes[i].xrule),im->yorigin,
2879 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2880 1.0,im->gdes[i].col);
2888 if (strcmp(im->graphfile,"-")==0) {
2889 fo = im->graphhandle ? im->graphhandle : stdout;
2890 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2891 /* Change translation mode for stdout to BINARY */
2892 _setmode( _fileno( fo ), O_BINARY );
2895 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2896 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2897 rrd_strerror(errno));
2901 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2902 if (strcmp(im->graphfile,"-") != 0)
2908 /*****************************************************
2910 *****************************************************/
2913 gdes_alloc(image_desc_t *im){
2916 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2917 * sizeof(graph_desc_t)))==NULL){
2918 rrd_set_error("realloc graph_descs");
2923 im->gdes[im->gdes_c-1].step=im->step;
2924 im->gdes[im->gdes_c-1].step_orig=im->step;
2925 im->gdes[im->gdes_c-1].stack=0;
2926 im->gdes[im->gdes_c-1].linewidth=0;
2927 im->gdes[im->gdes_c-1].debug=0;
2928 im->gdes[im->gdes_c-1].start=im->start;
2929 im->gdes[im->gdes_c-1].start_orig=im->start;
2930 im->gdes[im->gdes_c-1].end=im->end;
2931 im->gdes[im->gdes_c-1].end_orig=im->end;
2932 im->gdes[im->gdes_c-1].vname[0]='\0';
2933 im->gdes[im->gdes_c-1].data=NULL;
2934 im->gdes[im->gdes_c-1].ds_namv=NULL;
2935 im->gdes[im->gdes_c-1].data_first=0;
2936 im->gdes[im->gdes_c-1].p_data=NULL;
2937 im->gdes[im->gdes_c-1].rpnp=NULL;
2938 im->gdes[im->gdes_c-1].shift=0;
2939 im->gdes[im->gdes_c-1].col = 0x0;
2940 im->gdes[im->gdes_c-1].legend[0]='\0';
2941 im->gdes[im->gdes_c-1].format[0]='\0';
2942 im->gdes[im->gdes_c-1].strftm=0;
2943 im->gdes[im->gdes_c-1].rrd[0]='\0';
2944 im->gdes[im->gdes_c-1].ds=-1;
2945 im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;
2946 im->gdes[im->gdes_c-1].cf=CF_AVERAGE;
2947 im->gdes[im->gdes_c-1].p_data=NULL;
2948 im->gdes[im->gdes_c-1].yrule=DNAN;
2949 im->gdes[im->gdes_c-1].xrule=0;
2953 /* copies input untill the first unescaped colon is found
2954 or until input ends. backslashes have to be escaped as well */
2956 scan_for_col(const char *const input, int len, char *const output)
2961 input[inp] != ':' &&
2964 if (input[inp] == '\\' &&
2965 input[inp+1] != '\0' &&
2966 (input[inp+1] == '\\' ||
2967 input[inp+1] == ':')){
2968 output[outp++] = input[++inp];
2971 output[outp++] = input[inp];
2974 output[outp] = '\0';
2977 /* Some surgery done on this function, it became ridiculously big.
2979 ** - initializing now in rrd_graph_init()
2980 ** - options parsing now in rrd_graph_options()
2981 ** - script parsing now in rrd_graph_script()
2984 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
2987 rrd_graph_init(&im);
2988 im.graphhandle = stream;
2990 rrd_graph_options(argc,argv,&im);
2991 if (rrd_test_error()) {
2996 if (strlen(argv[optind])>=MAXPATH) {
2997 rrd_set_error("filename (including path) too long");
3001 strncpy(im.graphfile,argv[optind],MAXPATH-1);
3002 im.graphfile[MAXPATH-1]='\0';
3004 rrd_graph_script(argc,argv,&im,1);
3005 if (rrd_test_error()) {
3010 /* Everything is now read and the actual work can start */
3013 if (graph_paint(&im,prdata)==-1){
3018 /* The image is generated and needs to be output.
3019 ** Also, if needed, print a line with information about the image.
3029 /* maybe prdata is not allocated yet ... lets do it now */
3030 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
3031 rrd_set_error("malloc imginfo");
3035 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3037 rrd_set_error("malloc imginfo");
3040 filename=im.graphfile+strlen(im.graphfile);
3041 while(filename > im.graphfile) {
3042 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3046 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
3053 rrd_graph_init(image_desc_t *im)
3060 #ifdef HAVE_SETLOCALE
3061 setlocale(LC_TIME,"");
3062 #ifdef HAVE_MBSTOWCS
3063 setlocale(LC_CTYPE,"");
3069 im->xlab_user.minsec = -1;
3075 im->ylegend[0] = '\0';
3076 im->title[0] = '\0';
3077 im->watermark[0] = '\0';
3080 im->unitsexponent= 9999;
3083 im->viewfactor = 1.0;
3090 im->logarithmic = 0;
3091 im->ygridstep = DNAN;
3092 im->draw_x_grid = 1;
3093 im->draw_y_grid = 1;
3098 im->canvas = gfx_new_canvas();
3099 im->grid_dash_on = 1;
3100 im->grid_dash_off = 1;
3101 im->tabwidth = 40.0;
3103 for(i=0;i<DIM(graph_col);i++)
3104 im->graph_col[i]=graph_col[i];
3106 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3109 char rrd_win_default_font[1000];
3110 windir = getenv("windir");
3111 /* %windir% is something like D:\windows or C:\winnt */
3112 if (windir != NULL) {
3113 strncpy(rrd_win_default_font,windir,500);
3114 rrd_win_default_font[500] = '\0';
3115 strcat(rrd_win_default_font,"\\fonts\\");
3116 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
3117 for(i=0;i<DIM(text_prop);i++){
3118 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3119 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3126 deffont = getenv("RRD_DEFAULT_FONT");
3127 if (deffont != NULL) {
3128 for(i=0;i<DIM(text_prop);i++){
3129 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3130 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3134 for(i=0;i<DIM(text_prop);i++){
3135 im->text_prop[i].size = text_prop[i].size;
3136 strcpy(im->text_prop[i].font,text_prop[i].font);
3141 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3144 char *parsetime_error = NULL;
3145 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3146 time_t start_tmp=0,end_tmp=0;
3148 struct rrd_time_value start_tv, end_tv;
3150 optind = 0; opterr = 0; /* initialize getopt */
3152 parsetime("end-24h", &start_tv);
3153 parsetime("now", &end_tv);
3155 /* defines for long options without a short equivalent. should be bytes,
3156 and may not collide with (the ASCII value of) short options */
3157 #define LONGOPT_UNITS_SI 255
3160 static struct option long_options[] =
3162 {"start", required_argument, 0, 's'},
3163 {"end", required_argument, 0, 'e'},
3164 {"x-grid", required_argument, 0, 'x'},
3165 {"y-grid", required_argument, 0, 'y'},
3166 {"vertical-label",required_argument,0,'v'},
3167 {"width", required_argument, 0, 'w'},
3168 {"height", required_argument, 0, 'h'},
3169 {"interlaced", no_argument, 0, 'i'},
3170 {"upper-limit",required_argument, 0, 'u'},
3171 {"lower-limit",required_argument, 0, 'l'},
3172 {"rigid", no_argument, 0, 'r'},
3173 {"base", required_argument, 0, 'b'},
3174 {"logarithmic",no_argument, 0, 'o'},
3175 {"color", required_argument, 0, 'c'},
3176 {"font", required_argument, 0, 'n'},
3177 {"title", required_argument, 0, 't'},
3178 {"imginfo", required_argument, 0, 'f'},
3179 {"imgformat", required_argument, 0, 'a'},
3180 {"lazy", no_argument, 0, 'z'},
3181 {"zoom", required_argument, 0, 'm'},
3182 {"no-legend", no_argument, 0, 'g'},
3183 {"force-rules-legend",no_argument,0, 'F'},
3184 {"only-graph", no_argument, 0, 'j'},
3185 {"alt-y-grid", no_argument, 0, 'Y'},
3186 {"no-minor", no_argument, 0, 'I'},
3187 {"slope-mode", no_argument, 0, 'E'},
3188 {"alt-autoscale", no_argument, 0, 'A'},
3189 {"alt-autoscale-max", no_argument, 0, 'M'},
3190 {"no-gridfit", no_argument, 0, 'N'},
3191 {"units-exponent",required_argument, 0, 'X'},
3192 {"units-length",required_argument, 0, 'L'},
3193 {"units", required_argument, 0, LONGOPT_UNITS_SI },
3194 {"step", required_argument, 0, 'S'},
3195 {"tabwidth", required_argument, 0, 'T'},
3196 {"font-render-mode", required_argument, 0, 'R'},
3197 {"font-smoothing-threshold", required_argument, 0, 'B'},
3198 {"watermark", required_argument, 0, 'W'},
3199 {"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 */
3201 int option_index = 0;
3203 int col_start,col_end;
3205 opt = getopt_long(argc, argv,
3206 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3207 long_options, &option_index);
3214 im->extra_flags |= NOMINOR;
3217 im->extra_flags |= ALTYGRID;
3220 im->extra_flags |= ALTAUTOSCALE;
3223 im->extra_flags |= ALTAUTOSCALE_MAX;
3226 im->extra_flags |= ONLY_GRAPH;
3229 im->extra_flags |= NOLEGEND;
3232 im->extra_flags |= FORCE_RULES_LEGEND;
3234 case LONGOPT_UNITS_SI:
3235 if(im->extra_flags & FORCE_UNITS) {
3236 rrd_set_error("--units can only be used once!");
3239 if(strcmp(optarg,"si")==0)
3240 im->extra_flags |= FORCE_UNITS_SI;
3242 rrd_set_error("invalid argument for --units: %s", optarg );
3247 im->unitsexponent = atoi(optarg);
3250 im->unitslength = atoi(optarg);
3253 im->tabwidth = atof(optarg);
3256 im->step = atoi(optarg);
3262 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3263 rrd_set_error( "start time: %s", parsetime_error );
3268 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3269 rrd_set_error( "end time: %s", parsetime_error );
3274 if(strcmp(optarg,"none") == 0){
3280 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3282 &im->xlab_user.gridst,
3284 &im->xlab_user.mgridst,
3286 &im->xlab_user.labst,
3287 &im->xlab_user.precis,
3288 &stroff) == 7 && stroff != 0){
3289 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3290 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3291 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3292 rrd_set_error("unknown keyword %s",scan_gtm);
3294 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3295 rrd_set_error("unknown keyword %s",scan_mtm);
3297 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3298 rrd_set_error("unknown keyword %s",scan_ltm);
3301 im->xlab_user.minsec = 1;
3302 im->xlab_user.stst = im->xlab_form;
3304 rrd_set_error("invalid x-grid format");
3310 if(strcmp(optarg,"none") == 0){
3318 &im->ylabfact) == 2) {
3319 if(im->ygridstep<=0){
3320 rrd_set_error("grid step must be > 0");
3322 } else if (im->ylabfact < 1){
3323 rrd_set_error("label factor must be > 0");
3327 rrd_set_error("invalid y-grid format");
3332 strncpy(im->ylegend,optarg,150);
3333 im->ylegend[150]='\0';
3336 im->maxval = atof(optarg);
3339 im->minval = atof(optarg);
3342 im->base = atol(optarg);
3343 if(im->base != 1024 && im->base != 1000 ){
3344 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3349 long_tmp = atol(optarg);
3350 if (long_tmp < 10) {
3351 rrd_set_error("width below 10 pixels");
3354 im->xsize = long_tmp;
3357 long_tmp = atol(optarg);
3358 if (long_tmp < 10) {
3359 rrd_set_error("height below 10 pixels");
3362 im->ysize = long_tmp;
3365 im->canvas->interlaced = 1;
3371 im->imginfo = optarg;
3374 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3375 rrd_set_error("unsupported graphics format '%s'",optarg);
3387 im->logarithmic = 1;
3391 "%10[A-Z]#%n%8lx%n",
3392 col_nam,&col_start,&color,&col_end) == 2){
3394 int col_len = col_end - col_start;
3398 ((color & 0xF00) * 0x110000) |
3399 ((color & 0x0F0) * 0x011000) |
3400 ((color & 0x00F) * 0x001100) |
3406 ((color & 0xF000) * 0x11000) |
3407 ((color & 0x0F00) * 0x01100) |
3408 ((color & 0x00F0) * 0x00110) |
3409 ((color & 0x000F) * 0x00011)
3413 color = (color << 8) + 0xff /* shift left by 8 */;
3418 rrd_set_error("the color format is #RRGGBB[AA]");
3421 if((ci=grc_conv(col_nam)) != -1){
3422 im->graph_col[ci]=color;
3424 rrd_set_error("invalid color name '%s'",col_nam);
3428 rrd_set_error("invalid color def format");
3435 char font[1024] = "";
3438 "%10[A-Z]:%lf:%1000s",
3439 prop,&size,font) >= 2){
3441 if((sindex=text_prop_conv(prop)) != -1){
3442 for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){
3444 im->text_prop[propidx].size=size;
3446 if (strlen(font) > 0){
3447 strcpy(im->text_prop[propidx].font,font);
3449 if (propidx==sindex && sindex != 0) break;
3452 rrd_set_error("invalid fonttag '%s'",prop);
3456 rrd_set_error("invalid text property format");
3462 im->canvas->zoom = atof(optarg);
3463 if (im->canvas->zoom <= 0.0) {
3464 rrd_set_error("zoom factor must be > 0");
3469 strncpy(im->title,optarg,150);
3470 im->title[150]='\0';
3474 if ( strcmp( optarg, "normal" ) == 0 )
3475 im->canvas->aa_type = AA_NORMAL;
3476 else if ( strcmp( optarg, "light" ) == 0 )
3477 im->canvas->aa_type = AA_LIGHT;
3478 else if ( strcmp( optarg, "mono" ) == 0 )
3479 im->canvas->aa_type = AA_NONE;
3482 rrd_set_error("unknown font-render-mode '%s'", optarg );
3488 im->canvas->font_aa_threshold = atof(optarg);
3492 strncpy(im->watermark,optarg,100);
3493 im->watermark[99]='\0';
3498 rrd_set_error("unknown option '%c'", optopt);
3500 rrd_set_error("unknown option '%s'",argv[optind-1]);
3505 if (optind >= argc) {
3506 rrd_set_error("missing filename");
3510 if (im->logarithmic == 1 && im->minval <= 0){
3511 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3515 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3516 /* error string is set in parsetime.c */
3520 if (start_tmp < 3600*24*365*10){
3521 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3525 if (end_tmp < start_tmp) {
3526 rrd_set_error("start (%ld) should be less than end (%ld)",
3527 start_tmp, end_tmp);
3531 im->start = start_tmp;
3533 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3537 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3539 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3540 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3546 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3549 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3551 color=strstr(var,"#");
3554 rrd_set_error("Found no color in %s",err);
3563 rest=strstr(color,":");
3571 sscanf(color,"#%6lx%n",&col,&n);
3572 col = (col << 8) + 0xff /* shift left by 8 */;
3573 if (n!=7) rrd_set_error("Color problem in %s",err);
3576 sscanf(color,"#%8lx%n",&col,&n);
3579 rrd_set_error("Color problem in %s",err);
3581 if (rrd_test_error()) return 0;
3588 int bad_format(char *fmt) {
3592 while (*ptr != '\0')
3593 if (*ptr++ == '%') {
3595 /* line cannot end with percent char */
3596 if (*ptr == '\0') return 1;
3598 /* '%s', '%S' and '%%' are allowed */
3599 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3601 /* %c is allowed (but use only with vdef!) */
3602 else if (*ptr == 'c') {
3607 /* or else '% 6.2lf' and such are allowed */
3609 /* optional padding character */
3610 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3612 /* This should take care of 'm.n' with all three optional */
3613 while (*ptr >= '0' && *ptr <= '9') ptr++;
3614 if (*ptr == '.') ptr++;
3615 while (*ptr >= '0' && *ptr <= '9') ptr++;
3617 /* Either 'le', 'lf' or 'lg' must follow here */
3618 if (*ptr++ != 'l') return 1;
3619 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3630 vdef_parse(gdes,str)
3631 struct graph_desc_t *gdes;
3632 const char *const str;
3634 /* A VDEF currently is either "func" or "param,func"
3635 * so the parsing is rather simple. Change if needed.
3642 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3643 if (n== (int)strlen(str)) { /* matched */
3647 sscanf(str,"%29[A-Z]%n",func,&n);
3648 if (n== (int)strlen(str)) { /* matched */
3651 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3658 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3659 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3660 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3661 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3662 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3663 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3664 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3665 else if (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3666 else if (!strcmp("LSLINT", func)) gdes->vf.op = VDEF_LSLINT;
3667 else if (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3669 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3676 switch (gdes->vf.op) {
3678 if (isnan(param)) { /* no parameter given */
3679 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3685 if (param>=0.0 && param<=100.0) {
3686 gdes->vf.param = param;
3687 gdes->vf.val = DNAN; /* undefined */
3688 gdes->vf.when = 0; /* undefined */
3690 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3705 case VDEF_LSLCORREL:
3707 gdes->vf.param = DNAN;
3708 gdes->vf.val = DNAN;
3711 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3728 graph_desc_t *src,*dst;
3732 dst = &im->gdes[gdi];
3733 src = &im->gdes[dst->vidx];
3734 data = src->data + src->ds;
3735 steps = (src->end - src->start) / src->step;
3738 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3745 switch (dst->vf.op) {
3746 case VDEF_PERCENT: {
3747 rrd_value_t * array;
3751 if ((array = malloc(steps*sizeof(double)))==NULL) {
3752 rrd_set_error("malloc VDEV_PERCENT");
3755 for (step=0;step < steps; step++) {
3756 array[step]=data[step*src->ds_cnt];
3758 qsort(array,step,sizeof(double),vdef_percent_compar);
3760 field = (steps-1)*dst->vf.param/100;
3761 dst->vf.val = array[field];
3762 dst->vf.when = 0; /* no time component */
3765 for(step=0;step<steps;step++)
3766 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3772 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3773 if (step == steps) {
3777 dst->vf.val = data[step*src->ds_cnt];
3778 dst->vf.when = src->start + (step+1)*src->step;
3780 while (step != steps) {
3781 if (finite(data[step*src->ds_cnt])) {
3782 if (data[step*src->ds_cnt] > dst->vf.val) {
3783 dst->vf.val = data[step*src->ds_cnt];
3784 dst->vf.when = src->start + (step+1)*src->step;
3791 case VDEF_AVERAGE: {
3794 for (step=0;step<steps;step++) {
3795 if (finite(data[step*src->ds_cnt])) {
3796 sum += data[step*src->ds_cnt];
3801 if (dst->vf.op == VDEF_TOTAL) {
3802 dst->vf.val = sum*src->step;
3803 dst->vf.when = cnt*src->step; /* not really "when" */
3805 dst->vf.val = sum/cnt;
3806 dst->vf.when = 0; /* no time component */
3816 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3817 if (step == steps) {
3821 dst->vf.val = data[step*src->ds_cnt];
3822 dst->vf.when = src->start + (step+1)*src->step;
3824 while (step != steps) {
3825 if (finite(data[step*src->ds_cnt])) {
3826 if (data[step*src->ds_cnt] < dst->vf.val) {
3827 dst->vf.val = data[step*src->ds_cnt];
3828 dst->vf.when = src->start + (step+1)*src->step;
3835 /* The time value returned here is one step before the
3836 * actual time value. This is the start of the first
3840 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3841 if (step == steps) { /* all entries were NaN */
3845 dst->vf.val = data[step*src->ds_cnt];
3846 dst->vf.when = src->start + step*src->step;
3850 /* The time value returned here is the
3851 * actual time value. This is the end of the last
3855 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3856 if (step < 0) { /* all entries were NaN */
3860 dst->vf.val = data[step*src->ds_cnt];
3861 dst->vf.when = src->start + (step+1)*src->step;
3866 case VDEF_LSLCORREL:{
3867 /* Bestfit line by linear least squares method */
3870 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3871 SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3873 for (step=0;step<steps;step++) {
3874 if (finite(data[step*src->ds_cnt])) {
3877 SUMxx += step * step;
3878 SUMxy += step * data[step*src->ds_cnt];
3879 SUMy += data[step*src->ds_cnt];
3880 SUMyy += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3884 slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3885 y_intercept = ( SUMy - slope*SUMx ) / cnt;
3886 correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3889 if (dst->vf.op == VDEF_LSLSLOPE) {
3890 dst->vf.val = slope;
3891 dst->vf.when = cnt*src->step;
3892 } else if (dst->vf.op == VDEF_LSLINT) {
3893 dst->vf.val = y_intercept;
3894 dst->vf.when = cnt*src->step;
3895 } else if (dst->vf.op == VDEF_LSLCORREL) {
3896 dst->vf.val = correl;
3897 dst->vf.when = cnt*src->step;
3910 /* NaN < -INF < finite_values < INF */
3912 vdef_percent_compar(a,b)
3915 /* Equality is not returned; this doesn't hurt except
3916 * (maybe) for a little performance.
3919 /* First catch NaN values. They are smallest */
3920 if (isnan( *(double *)a )) return -1;
3921 if (isnan( *(double *)b )) return 1;
3923 /* NaN doesn't reach this part so INF and -INF are extremes.
3924 * The sign from isinf() is compatible with the sign we return
3926 if (isinf( *(double *)a )) return isinf( *(double *)a );
3927 if (isinf( *(double *)b )) return isinf( *(double *)b );
3929 /* If we reach this, both values must be finite */
3930 if ( *(double *)a < *(double *)b ) return -1; else return 1;