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 if (im->imginfo) prlines++;
1239 for(i=0;i<im->gdes_c;i++){
1240 switch(im->gdes[i].gf){
1243 if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1244 rrd_set_error("realloc prdata");
1248 /* PRINT and GPRINT can now print VDEF generated values.
1249 * There's no need to do any calculations on them as these
1250 * calculations were already made.
1252 vidx = im->gdes[i].vidx;
1253 if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1254 printval = im->gdes[vidx].vf.val;
1255 printtime = im->gdes[vidx].vf.when;
1256 } else { /* need to calculate max,min,avg etcetera */
1257 max_ii =((im->gdes[vidx].end
1258 - im->gdes[vidx].start)
1259 / im->gdes[vidx].step
1260 * im->gdes[vidx].ds_cnt);
1263 for( ii=im->gdes[vidx].ds;
1265 ii+=im->gdes[vidx].ds_cnt){
1266 if (! finite(im->gdes[vidx].data[ii]))
1268 if (isnan(printval)){
1269 printval = im->gdes[vidx].data[ii];
1274 switch (im->gdes[i].cf){
1277 case CF_DEVSEASONAL:
1281 printval += im->gdes[vidx].data[ii];
1284 printval = min( printval, im->gdes[vidx].data[ii]);
1288 printval = max( printval, im->gdes[vidx].data[ii]);
1291 printval = im->gdes[vidx].data[ii];
1294 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1295 if (validsteps > 1) {
1296 printval = (printval / validsteps);
1299 } /* prepare printval */
1301 if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1302 char ctime_buf[128]; /* PS: for ctime_r, must be >= 26 chars */
1304 ctime_r(&printtime,ctime_buf);
1305 while(isprint(ctime_buf[iii])){iii++;}
1306 ctime_buf[iii]='\0';
1307 if (im->gdes[i].gf == GF_PRINT){
1308 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1309 sprintf((*prdata)[prlines-2],"%s (%lu)",ctime_buf,printtime);
1310 (*prdata)[prlines-1] = NULL;
1312 sprintf(im->gdes[i].legend,"%s (%lu)",ctime_buf,printtime);
1316 if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1317 /* Magfact is set to -1 upon entry to print_calc. If it
1318 * is still less than 0, then we need to run auto_scale.
1319 * Otherwise, put the value into the correct units. If
1320 * the value is 0, then do not set the symbol or magnification
1321 * so next the calculation will be performed again. */
1322 if (magfact < 0.0) {
1323 auto_scale(im,&printval,&si_symb,&magfact);
1324 if (printval == 0.0)
1327 printval /= magfact;
1329 *(++percent_s) = 's';
1330 } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1331 auto_scale(im,&printval,&si_symb,&magfact);
1334 if (im->gdes[i].gf == GF_PRINT){
1335 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1336 (*prdata)[prlines-1] = NULL;
1337 if (bad_format(im->gdes[i].format)) {
1338 rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1341 #ifdef HAVE_SNPRINTF
1342 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1344 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1349 if (bad_format(im->gdes[i].format)) {
1350 rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1353 #ifdef HAVE_SNPRINTF
1354 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1356 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1373 #ifdef WITH_PIECHART
1380 rrd_set_error("STACK should already be turned into LINE or AREA here");
1385 return graphelement;
1389 /* place legends with color spots */
1391 leg_place(image_desc_t *im)
1394 int interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1395 int border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1396 int fill=0, fill_last;
1398 int leg_x = border, leg_y = im->yimg;
1399 int leg_y_prev = im->yimg;
1403 char prt_fctn; /*special printfunctions */
1406 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1407 if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1408 rrd_set_error("malloc for legspace");
1412 for(i=0;i<im->gdes_c;i++){
1415 /* hid legends for rules which are not displayed */
1417 if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1418 if (im->gdes[i].gf == GF_HRULE &&
1419 (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1420 im->gdes[i].legend[0] = '\0';
1422 if (im->gdes[i].gf == GF_VRULE &&
1423 (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1424 im->gdes[i].legend[0] = '\0';
1427 leg_cc = strlen(im->gdes[i].legend);
1429 /* is there a controle code ant the end of the legend string ? */
1430 /* and it is not a tab \\t */
1431 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1432 prt_fctn = im->gdes[i].legend[leg_cc-1];
1434 im->gdes[i].legend[leg_cc] = '\0';
1438 /* remove exess space */
1439 while (prt_fctn=='g' &&
1441 im->gdes[i].legend[leg_cc-1]==' '){
1443 im->gdes[i].legend[leg_cc]='\0';
1446 legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1449 /* no interleg space if string ends in \g */
1450 fill += legspace[i];
1452 fill += gfx_get_text_width(im->canvas, fill+border,
1453 im->text_prop[TEXT_PROP_LEGEND].font,
1454 im->text_prop[TEXT_PROP_LEGEND].size,
1456 im->gdes[i].legend, 0);
1461 /* who said there was a special tag ... ?*/
1462 if (prt_fctn=='g') {
1465 if (prt_fctn == '\0') {
1466 if (i == im->gdes_c -1 ) prt_fctn ='l';
1468 /* is it time to place the legends ? */
1469 if (fill > im->ximg - 2*border){
1484 if (prt_fctn != '\0'){
1486 if (leg_c >= 2 && prt_fctn == 'j') {
1487 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1491 if (prt_fctn =='c') leg_x = (im->ximg - fill) / 2.0;
1492 if (prt_fctn =='r') leg_x = im->ximg - fill - border;
1494 for(ii=mark;ii<=i;ii++){
1495 if(im->gdes[ii].legend[0]=='\0')
1496 continue; /* skip empty legends */
1497 im->gdes[ii].leg_x = leg_x;
1498 im->gdes[ii].leg_y = leg_y;
1500 gfx_get_text_width(im->canvas, leg_x,
1501 im->text_prop[TEXT_PROP_LEGEND].font,
1502 im->text_prop[TEXT_PROP_LEGEND].size,
1504 im->gdes[ii].legend, 0)
1509 /* only add y space if there was text on the line */
1510 if (leg_x > border || prt_fctn == 's')
1511 leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1512 if (prt_fctn == 's')
1513 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1519 im->yimg = leg_y_prev;
1520 /* if we did place some legends we have to add vertical space */
1521 if (leg_y != im->yimg){
1522 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1529 /* create a grid on the graph. it determines what to do
1530 from the values of xsize, start and end */
1532 /* the xaxis labels are determined from the number of seconds per pixel
1533 in the requested graph */
1538 calc_horizontal_grid(image_desc_t *im)
1544 int decimals, fractionals;
1546 im->ygrid_scale.labfact=2;
1547 range = im->maxval - im->minval;
1548 scaledrange = range / im->magfact;
1550 /* does the scale of this graph make it impossible to put lines
1551 on it? If so, give up. */
1552 if (isnan(scaledrange)) {
1556 /* find grid spaceing */
1558 if(isnan(im->ygridstep)){
1559 if(im->extra_flags & ALTYGRID) {
1560 /* find the value with max number of digits. Get number of digits */
1561 decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1562 if(decimals <= 0) /* everything is small. make place for zero */
1565 im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1567 if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1568 im->ygrid_scale.gridstep = 0.1;
1569 /* should have at least 5 lines but no more then 15 */
1570 if(range/im->ygrid_scale.gridstep < 5)
1571 im->ygrid_scale.gridstep /= 10;
1572 if(range/im->ygrid_scale.gridstep > 15)
1573 im->ygrid_scale.gridstep *= 10;
1574 if(range/im->ygrid_scale.gridstep > 5) {
1575 im->ygrid_scale.labfact = 1;
1576 if(range/im->ygrid_scale.gridstep > 8)
1577 im->ygrid_scale.labfact = 2;
1580 im->ygrid_scale.gridstep /= 5;
1581 im->ygrid_scale.labfact = 5;
1583 fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1584 if(fractionals < 0) { /* small amplitude. */
1585 int len = decimals - fractionals + 1;
1586 if (im->unitslength < len+2) im->unitslength = len+2;
1587 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1589 int len = decimals + 1;
1590 if (im->unitslength < len+2) im->unitslength = len+2;
1591 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1595 for(i=0;ylab[i].grid > 0;i++){
1596 pixel = im->ysize / (scaledrange / ylab[i].grid);
1603 if (pixel * ylab[gridind].lfac[i] >= 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1604 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1609 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1612 im->ygrid_scale.gridstep = im->ygridstep;
1613 im->ygrid_scale.labfact = im->ylabfact;
1618 int draw_horizontal_grid(image_desc_t *im)
1622 char graph_label[100];
1624 double X0=im->xorigin;
1625 double X1=im->xorigin+im->xsize;
1627 int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1628 int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1630 scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1631 MaxY = scaledstep*(double)egrid;
1632 for (i = sgrid; i <= egrid; i++){
1633 double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1634 double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
1635 if ( Y0 >= im->yorigin-im->ysize
1636 && Y0 <= im->yorigin){
1637 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1638 with the chosen settings. Add a label if required by settings, or if
1639 there is only one label so far and the next grid line is out of bounds. */
1640 if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){
1641 if (im->symbol == ' ') {
1642 if(im->extra_flags & ALTYGRID) {
1643 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1646 sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1648 sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1652 char sisym = ( i == 0 ? ' ' : im->symbol);
1653 if(im->extra_flags & ALTYGRID) {
1654 sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1657 sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1659 sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1665 gfx_new_text ( im->canvas,
1666 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1667 im->graph_col[GRC_FONT],
1668 im->text_prop[TEXT_PROP_AXIS].font,
1669 im->text_prop[TEXT_PROP_AXIS].size,
1670 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1672 gfx_new_dashed_line ( im->canvas,
1675 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1676 im->grid_dash_on, im->grid_dash_off);
1678 } else if (!(im->extra_flags & NOMINOR)) {
1679 gfx_new_dashed_line ( im->canvas,
1682 GRIDWIDTH, im->graph_col[GRC_GRID],
1683 im->grid_dash_on, im->grid_dash_off);
1691 /* this is frexp for base 10 */
1692 double frexp10(double, double *);
1693 double frexp10(double x, double *e) {
1697 iexp = floor(log(fabs(x)) / log(10));
1698 mnt = x / pow(10.0, iexp);
1701 mnt = x / pow(10.0, iexp);
1707 /* logaritmic horizontal grid */
1709 horizontal_log_grid(image_desc_t *im)
1711 double yloglab[][10] = {
1712 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1713 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1714 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1715 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1716 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.}};
1718 int i, j, val_exp, min_exp;
1719 double nex; /* number of decades in data */
1720 double logscale; /* scale in logarithmic space */
1721 int exfrac = 1; /* decade spacing */
1722 int mid = -1; /* row in yloglab for major grid */
1723 double mspac; /* smallest major grid spacing (pixels) */
1724 int flab; /* first value in yloglab to use */
1727 char graph_label[100];
1729 nex = log10(im->maxval / im->minval);
1730 logscale = im->ysize / nex;
1732 /* major spacing for data with high dynamic range */
1733 while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1734 if(exfrac == 1) exfrac = 3;
1738 /* major spacing for less dynamic data */
1740 /* search best row in yloglab */
1742 for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
1743 mspac = logscale * log10(10.0 / yloglab[mid][i]);
1744 } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && mid < 5);
1747 /* find first value in yloglab */
1748 for(flab = 0; frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
1749 if(yloglab[mid][flab] == 10.0) {
1754 if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
1757 X1=im->xorigin+im->xsize;
1761 value = yloglab[mid][flab] * pow(10.0, val_exp);
1763 Y0 = ytr(im, value);
1764 if(Y0 <= im->yorigin - im->ysize) break;
1766 /* major grid line */
1767 gfx_new_dashed_line ( im->canvas,
1770 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1771 im->grid_dash_on, im->grid_dash_off);
1774 if (im->extra_flags & FORCE_UNITS_SI) {
1779 scale = floor(val_exp / 3.0);
1780 if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
1781 else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
1782 pvalue *= yloglab[mid][flab];
1784 if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
1785 ((scale+si_symbcenter) >= 0) )
1786 symbol = si_symbol[scale+si_symbcenter];
1790 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1792 sprintf(graph_label,"%3.0e", value);
1793 gfx_new_text ( im->canvas,
1794 X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1795 im->graph_col[GRC_FONT],
1796 im->text_prop[TEXT_PROP_AXIS].font,
1797 im->text_prop[TEXT_PROP_AXIS].size,
1798 im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1802 if(mid < 4 && exfrac == 1) {
1803 /* find first and last minor line behind current major line
1804 * i is the first line and j tha last */
1806 min_exp = val_exp - 1;
1807 for(i = 1; yloglab[mid][i] < 10.0; i++);
1808 i = yloglab[mid][i - 1] + 1;
1813 i = yloglab[mid][flab - 1] + 1;
1814 j = yloglab[mid][flab];
1817 /* draw minor lines below current major line */
1820 value = i * pow(10.0, min_exp);
1821 if(value < im->minval) continue;
1823 Y0 = ytr(im, value);
1824 if(Y0 <= im->yorigin - im->ysize) break;
1827 gfx_new_dashed_line ( im->canvas,
1830 GRIDWIDTH, im->graph_col[GRC_GRID],
1831 im->grid_dash_on, im->grid_dash_off);
1834 else if(exfrac > 1) {
1835 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1836 value = pow(10.0, i);
1837 if(value < im->minval) continue;
1839 Y0 = ytr(im, value);
1840 if(Y0 <= im->yorigin - im->ysize) break;
1843 gfx_new_dashed_line ( im->canvas,
1846 GRIDWIDTH, im->graph_col[GRC_GRID],
1847 im->grid_dash_on, im->grid_dash_off);
1852 if(yloglab[mid][++flab] == 10.0) {
1858 /* draw minor lines after highest major line */
1859 if(mid < 4 && exfrac == 1) {
1860 /* find first and last minor line below current major line
1861 * i is the first line and j tha last */
1863 min_exp = val_exp - 1;
1864 for(i = 1; yloglab[mid][i] < 10.0; i++);
1865 i = yloglab[mid][i - 1] + 1;
1870 i = yloglab[mid][flab - 1] + 1;
1871 j = yloglab[mid][flab];
1874 /* draw minor lines below current major line */
1877 value = i * pow(10.0, min_exp);
1878 if(value < im->minval) continue;
1880 Y0 = ytr(im, value);
1881 if(Y0 <= im->yorigin - im->ysize) break;
1884 gfx_new_dashed_line ( im->canvas,
1887 GRIDWIDTH, im->graph_col[GRC_GRID],
1888 im->grid_dash_on, im->grid_dash_off);
1891 /* fancy minor gridlines */
1892 else if(exfrac > 1) {
1893 for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1894 value = pow(10.0, i);
1895 if(value < im->minval) continue;
1897 Y0 = ytr(im, value);
1898 if(Y0 <= im->yorigin - im->ysize) break;
1901 gfx_new_dashed_line ( im->canvas,
1904 GRIDWIDTH, im->graph_col[GRC_GRID],
1905 im->grid_dash_on, im->grid_dash_off);
1917 int xlab_sel; /* which sort of label and grid ? */
1918 time_t ti, tilab, timajor;
1920 char graph_label[100];
1921 double X0,Y0,Y1; /* points for filled graph and more*/
1924 /* the type of time grid is determined by finding
1925 the number of seconds per pixel in the graph */
1928 if(im->xlab_user.minsec == -1){
1929 factor=(im->end - im->start)/im->xsize;
1931 while ( xlab[xlab_sel+1].minsec != -1
1932 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; } /* pick the last one */
1933 while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
1934 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; } /* go back to the smallest size */
1935 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1936 im->xlab_user.gridst = xlab[xlab_sel].gridst;
1937 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1938 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1939 im->xlab_user.labtm = xlab[xlab_sel].labtm;
1940 im->xlab_user.labst = xlab[xlab_sel].labst;
1941 im->xlab_user.precis = xlab[xlab_sel].precis;
1942 im->xlab_user.stst = xlab[xlab_sel].stst;
1945 /* y coords are the same for every line ... */
1947 Y1 = im->yorigin-im->ysize;
1950 /* paint the minor grid */
1951 if (!(im->extra_flags & NOMINOR))
1953 for(ti = find_first_time(im->start,
1954 im->xlab_user.gridtm,
1955 im->xlab_user.gridst),
1956 timajor = find_first_time(im->start,
1957 im->xlab_user.mgridtm,
1958 im->xlab_user.mgridst);
1960 ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1962 /* are we inside the graph ? */
1963 if (ti < im->start || ti > im->end) continue;
1964 while (timajor < ti) {
1965 timajor = find_next_time(timajor,
1966 im->xlab_user.mgridtm, im->xlab_user.mgridst);
1968 if (ti == timajor) continue; /* skip as falls on major grid line */
1970 gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1971 im->graph_col[GRC_GRID],
1972 im->grid_dash_on, im->grid_dash_off);
1977 /* paint the major grid */
1978 for(ti = find_first_time(im->start,
1979 im->xlab_user.mgridtm,
1980 im->xlab_user.mgridst);
1982 ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1984 /* are we inside the graph ? */
1985 if (ti < im->start || ti > im->end) continue;
1987 gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1988 im->graph_col[GRC_MGRID],
1989 im->grid_dash_on, im->grid_dash_off);
1992 /* paint the labels below the graph */
1993 for(ti = find_first_time(im->start - im->xlab_user.precis/2,
1994 im->xlab_user.labtm,
1995 im->xlab_user.labst);
1996 ti <= im->end - im->xlab_user.precis/2;
1997 ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1999 tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
2000 /* are we inside the graph ? */
2001 if (tilab < im->start || tilab > im->end) continue;
2004 localtime_r(&tilab, &tm);
2005 strftime(graph_label,99,im->xlab_user.stst, &tm);
2007 # error "your libc has no strftime I guess we'll abort the exercise here."
2009 gfx_new_text ( im->canvas,
2010 xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
2011 im->graph_col[GRC_FONT],
2012 im->text_prop[TEXT_PROP_AXIS].font,
2013 im->text_prop[TEXT_PROP_AXIS].size,
2014 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
2027 /* draw x and y axis */
2028 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2029 im->xorigin+im->xsize,im->yorigin-im->ysize,
2030 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2032 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2033 im->xorigin+im->xsize,im->yorigin-im->ysize,
2034 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2036 gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
2037 im->xorigin+im->xsize+4,im->yorigin,
2038 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2040 gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
2041 im->xorigin,im->yorigin-im->ysize-4,
2042 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2045 /* arrow for X and Y axis direction */
2046 gfx_new_area ( im->canvas,
2047 im->xorigin+im->xsize+2, im->yorigin-2,
2048 im->xorigin+im->xsize+2, im->yorigin+3,
2049 im->xorigin+im->xsize+7, im->yorigin+0.5, /* LINEOFFSET */
2050 im->graph_col[GRC_ARROW]);
2052 gfx_new_area ( im->canvas,
2053 im->xorigin-2, im->yorigin-im->ysize-2,
2054 im->xorigin+3, im->yorigin-im->ysize-2,
2055 im->xorigin+0.5, im->yorigin-im->ysize-7, /* LINEOFFSET */
2056 im->graph_col[GRC_ARROW]);
2061 grid_paint(image_desc_t *im)
2065 double X0,Y0; /* points for filled graph and more*/
2068 /* draw 3d border */
2069 node = gfx_new_area (im->canvas, 0,im->yimg,
2071 2,2,im->graph_col[GRC_SHADEA]);
2072 gfx_add_point( node , im->ximg - 2, 2 );
2073 gfx_add_point( node , im->ximg, 0 );
2074 gfx_add_point( node , 0,0 );
2075 /* gfx_add_point( node , 0,im->yimg ); */
2077 node = gfx_new_area (im->canvas, 2,im->yimg-2,
2078 im->ximg-2,im->yimg-2,
2080 im->graph_col[GRC_SHADEB]);
2081 gfx_add_point( node , im->ximg,0);
2082 gfx_add_point( node , im->ximg,im->yimg);
2083 gfx_add_point( node , 0,im->yimg);
2084 /* gfx_add_point( node , 0,im->yimg ); */
2087 if (im->draw_x_grid == 1 )
2090 if (im->draw_y_grid == 1){
2091 if(im->logarithmic){
2092 res = horizontal_log_grid(im);
2094 res = draw_horizontal_grid(im);
2097 /* dont draw horizontal grid if there is no min and max val */
2099 char *nodata = "No Data found";
2100 gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
2101 im->graph_col[GRC_FONT],
2102 im->text_prop[TEXT_PROP_AXIS].font,
2103 im->text_prop[TEXT_PROP_AXIS].size,
2104 im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
2109 /* yaxis unit description */
2110 gfx_new_text( im->canvas,
2111 10, (im->yorigin - im->ysize/2),
2112 im->graph_col[GRC_FONT],
2113 im->text_prop[TEXT_PROP_UNIT].font,
2114 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2115 RRDGRAPH_YLEGEND_ANGLE,
2116 GFX_H_LEFT, GFX_V_CENTER,
2120 gfx_new_text( im->canvas,
2121 im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2122 im->graph_col[GRC_FONT],
2123 im->text_prop[TEXT_PROP_TITLE].font,
2124 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2125 GFX_H_CENTER, GFX_V_CENTER,
2127 /* rrdtool 'logo' */
2128 gfx_new_text( im->canvas,
2130 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2131 im->text_prop[TEXT_PROP_AXIS].font,
2132 5.5, im->tabwidth, 270,
2133 GFX_H_RIGHT, GFX_V_TOP,
2134 "RRDTOOL / TOBI OETIKER");
2136 /* graph watermark */
2137 if(im->watermark[0] != '\0') {
2138 gfx_new_text( im->canvas,
2139 im->ximg/2, im->yimg-6,
2140 ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2141 im->text_prop[TEXT_PROP_AXIS].font,
2142 5.5, im->tabwidth, 0,
2143 GFX_H_CENTER, GFX_V_BOTTOM,
2148 if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2149 for(i=0;i<im->gdes_c;i++){
2150 if(im->gdes[i].legend[0] =='\0')
2153 /* im->gdes[i].leg_y is the bottom of the legend */
2154 X0 = im->gdes[i].leg_x;
2155 Y0 = im->gdes[i].leg_y;
2156 gfx_new_text ( im->canvas, X0, Y0,
2157 im->graph_col[GRC_FONT],
2158 im->text_prop[TEXT_PROP_LEGEND].font,
2159 im->text_prop[TEXT_PROP_LEGEND].size,
2160 im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2161 im->gdes[i].legend );
2162 /* The legend for GRAPH items starts with "M " to have
2163 enough space for the box */
2164 if ( im->gdes[i].gf != GF_PRINT &&
2165 im->gdes[i].gf != GF_GPRINT &&
2166 im->gdes[i].gf != GF_COMMENT) {
2169 boxH = gfx_get_text_width(im->canvas, 0,
2170 im->text_prop[TEXT_PROP_LEGEND].font,
2171 im->text_prop[TEXT_PROP_LEGEND].size,
2172 im->tabwidth,"o", 0) * 1.2;
2175 /* make sure transparent colors show up the same way as in the graph */
2176 node = gfx_new_area(im->canvas,
2180 im->graph_col[GRC_BACK]);
2181 gfx_add_point ( node, X0+boxH, Y0-boxV );
2183 node = gfx_new_area(im->canvas,
2188 gfx_add_point ( node, X0+boxH, Y0-boxV );
2189 node = gfx_new_line(im->canvas,
2192 1.0,im->graph_col[GRC_FRAME]);
2193 gfx_add_point(node,X0+boxH,Y0);
2194 gfx_add_point(node,X0+boxH,Y0-boxV);
2195 gfx_close_path(node);
2202 /*****************************************************
2203 * lazy check make sure we rely need to create this graph
2204 *****************************************************/
2206 int lazy_check(image_desc_t *im){
2209 struct stat imgstat;
2211 if (im->lazy == 0) return 0; /* no lazy option */
2212 if (stat(im->graphfile,&imgstat) != 0)
2213 return 0; /* can't stat */
2214 /* one pixel in the existing graph is more then what we would
2216 if (time(NULL) - imgstat.st_mtime >
2217 (im->end - im->start) / im->xsize)
2219 if ((fd = fopen(im->graphfile,"rb")) == NULL)
2220 return 0; /* the file does not exist */
2221 switch (im->canvas->imgformat) {
2223 size = PngSize(fd,&(im->ximg),&(im->yimg));
2232 #ifdef WITH_PIECHART
2234 pie_part(image_desc_t *im, gfx_color_t color,
2235 double PieCenterX, double PieCenterY, double Radius,
2236 double startangle, double endangle)
2240 double step=M_PI/50; /* Number of iterations for the circle;
2241 ** 10 is definitely too low, more than
2242 ** 50 seems to be overkill
2245 /* Strange but true: we have to work clockwise or else
2246 ** anti aliasing nor transparency don't work.
2248 ** This test is here to make sure we do it right, also
2249 ** this makes the for...next loop more easy to implement.
2250 ** The return will occur if the user enters a negative number
2251 ** (which shouldn't be done according to the specs) or if the
2252 ** programmers do something wrong (which, as we all know, never
2253 ** happens anyway :)
2255 if (endangle<startangle) return;
2257 /* Hidden feature: Radius decreases each full circle */
2259 while (angle>=2*M_PI) {
2264 node=gfx_new_area(im->canvas,
2265 PieCenterX+sin(startangle)*Radius,
2266 PieCenterY-cos(startangle)*Radius,
2269 PieCenterX+sin(endangle)*Radius,
2270 PieCenterY-cos(endangle)*Radius,
2272 for (angle=endangle;angle-startangle>=step;angle-=step) {
2274 PieCenterX+sin(angle)*Radius,
2275 PieCenterY-cos(angle)*Radius );
2282 graph_size_location(image_desc_t *im, int elements
2284 #ifdef WITH_PIECHART
2290 /* The actual size of the image to draw is determined from
2291 ** several sources. The size given on the command line is
2292 ** the graph area but we need more as we have to draw labels
2293 ** and other things outside the graph area
2296 /* +-+-------------------------------------------+
2297 ** |l|.................title.....................|
2298 ** |e+--+-------------------------------+--------+
2301 ** |l| l| main graph area | chart |
2304 ** |r+--+-------------------------------+--------+
2305 ** |e| | x-axis labels | |
2306 ** |v+--+-------------------------------+--------+
2307 ** | |..............legends......................|
2308 ** +-+-------------------------------------------+
2310 ** +---------------------------------------------+
2316 #ifdef WITH_PIECHART
2321 Xlegend =0, Ylegend =0,
2323 Xspacing =15, Yspacing =15,
2327 if (im->extra_flags & ONLY_GRAPH) {
2329 im->ximg = im->xsize;
2330 im->yimg = im->ysize;
2331 im->yorigin = im->ysize;
2336 if (im->ylegend[0] != '\0' ) {
2337 Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2341 if (im->title[0] != '\0') {
2342 /* The title is placed "inbetween" two text lines so it
2343 ** automatically has some vertical spacing. The horizontal
2344 ** spacing is added here, on each side.
2346 /* don't care for the with of the title
2347 Xtitle = gfx_get_text_width(im->canvas, 0,
2348 im->text_prop[TEXT_PROP_TITLE].font,
2349 im->text_prop[TEXT_PROP_TITLE].size,
2351 im->title, 0) + 2*Xspacing; */
2352 Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2358 if (im->draw_x_grid) {
2359 Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2361 if (im->draw_y_grid) {
2362 Xylabel=gfx_get_text_width(im->canvas, 0,
2363 im->text_prop[TEXT_PROP_AXIS].font,
2364 im->text_prop[TEXT_PROP_AXIS].size,
2366 "0", 0) * im->unitslength;
2370 #ifdef WITH_PIECHART
2372 im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2378 /* Now calculate the total size. Insert some spacing where
2379 desired. im->xorigin and im->yorigin need to correspond
2380 with the lower left corner of the main graph area or, if
2381 this one is not set, the imaginary box surrounding the
2384 /* The legend width cannot yet be determined, as a result we
2385 ** have problems adjusting the image to it. For now, we just
2386 ** forget about it at all; the legend will have to fit in the
2387 ** size already allocated.
2389 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2391 #ifdef WITH_PIECHART
2395 if (Xmain) im->ximg += Xspacing;
2396 #ifdef WITH_PIECHART
2397 if (Xpie) im->ximg += Xspacing;
2400 im->xorigin = Xspacing + Xylabel;
2402 /* the length of the title should not influence with width of the graph
2403 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2405 if (Xvertical) { /* unit description */
2406 im->ximg += Xvertical;
2407 im->xorigin += Xvertical;
2411 /* The vertical size is interesting... we need to compare
2412 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2413 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2414 ** in order to start even thinking about Ylegend or Ywatermark.
2416 ** Do it in three portions: First calculate the inner part,
2417 ** then do the legend, then adjust the total height of the img,
2418 ** adding space for a watermark if one exists;
2421 /* reserve space for main and/or pie */
2423 im->yimg = Ymain + Yxlabel;
2425 #ifdef WITH_PIECHART
2426 if (im->yimg < Ypie) im->yimg = Ypie;
2429 im->yorigin = im->yimg - Yxlabel;
2431 /* reserve space for the title *or* some padding above the graph */
2434 im->yorigin += Ytitle;
2436 im->yimg += 1.5*Yspacing;
2437 im->yorigin += 1.5*Yspacing;
2439 /* reserve space for padding below the graph */
2440 im->yimg += Yspacing;
2442 /* Determine where to place the legends onto the image.
2443 ** Adjust im->yimg to match the space requirements.
2445 if(leg_place(im)==-1)
2448 if (im->watermark[0] != '\0') {
2449 im->yimg += Ywatermark;
2453 if (Xlegend > im->ximg) {
2455 /* reposition Pie */
2459 #ifdef WITH_PIECHART
2460 /* The pie is placed in the upper right hand corner,
2461 ** just below the title (if any) and with sufficient
2465 im->pie_x = im->ximg - Xspacing - Xpie/2;
2466 im->pie_y = im->yorigin-Ymain+Ypie/2;
2468 im->pie_x = im->ximg/2;
2469 im->pie_y = im->yorigin-Ypie/2;
2477 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2478 /* yes we are loosing precision by doing tos with floats instead of doubles
2479 but it seems more stable this way. */
2481 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
2484 int aInt = *(int*)&A;
2485 int bInt = *(int*)&B;
2487 /* Make sure maxUlps is non-negative and small enough that the
2488 default NAN won't compare as equal to anything. */
2490 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
2492 /* Make aInt lexicographically ordered as a twos-complement int */
2495 aInt = 0x80000000l - aInt;
2497 /* Make bInt lexicographically ordered as a twos-complement int */
2500 bInt = 0x80000000l - bInt;
2502 intDiff = abs(aInt - bInt);
2504 if (intDiff <= maxUlps)
2510 /* draw that picture thing ... */
2512 graph_paint(image_desc_t *im, char ***calcpr)
2515 int lazy = lazy_check(im);
2516 #ifdef WITH_PIECHART
2518 double PieStart=0.0;
2523 double areazero = 0.0;
2524 graph_desc_t *lastgdes = NULL;
2526 /* if we are lazy and there is nothing to PRINT ... quit now */
2527 if (lazy && im->prt_c==0) return 0;
2529 /* pull the data from the rrd files ... */
2531 if(data_fetch(im)==-1)
2534 /* evaluate VDEF and CDEF operations ... */
2535 if(data_calc(im)==-1)
2538 #ifdef WITH_PIECHART
2539 /* check if we need to draw a piechart */
2540 for(i=0;i<im->gdes_c;i++){
2541 if (im->gdes[i].gf == GF_PART) {
2548 /* calculate and PRINT and GPRINT definitions. We have to do it at
2549 * this point because it will affect the length of the legends
2550 * if there are no graph elements we stop here ...
2551 * if we are lazy, try to quit ...
2553 i=print_calc(im,calcpr);
2556 #ifdef WITH_PIECHART
2559 ) || lazy) return 0;
2561 #ifdef WITH_PIECHART
2562 /* If there's only the pie chart to draw, signal this */
2563 if (i==0) piechart=2;
2566 /* get actual drawing data and find min and max values*/
2567 if(data_proc(im)==-1)
2570 if(!im->logarithmic){si_unit(im);} /* identify si magnitude Kilo, Mega Giga ? */
2572 if(!im->rigid && ! im->logarithmic)
2573 expand_range(im); /* make sure the upper and lower limit are
2576 if (!calc_horizontal_grid(im))
2583 /**************************************************************
2584 *** Calculating sizes and locations became a bit confusing ***
2585 *** so I moved this into a separate function. ***
2586 **************************************************************/
2587 if(graph_size_location(im,i
2588 #ifdef WITH_PIECHART
2594 /* the actual graph is created by going through the individual
2595 graph elements and then drawing them */
2597 node=gfx_new_area ( im->canvas,
2601 im->graph_col[GRC_BACK]);
2603 gfx_add_point(node,im->ximg, 0);
2605 #ifdef WITH_PIECHART
2606 if (piechart != 2) {
2608 node=gfx_new_area ( im->canvas,
2609 im->xorigin, im->yorigin,
2610 im->xorigin + im->xsize, im->yorigin,
2611 im->xorigin + im->xsize, im->yorigin-im->ysize,
2612 im->graph_col[GRC_CANVAS]);
2614 gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2616 if (im->minval > 0.0)
2617 areazero = im->minval;
2618 if (im->maxval < 0.0)
2619 areazero = im->maxval;
2620 #ifdef WITH_PIECHART
2624 #ifdef WITH_PIECHART
2626 pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2630 for(i=0;i<im->gdes_c;i++){
2631 switch(im->gdes[i].gf){
2644 for (ii = 0; ii < im->xsize; ii++)
2646 if (!isnan(im->gdes[i].p_data[ii]) &&
2647 im->gdes[i].p_data[ii] != 0.0)
2649 if (im -> gdes[i].yrule > 0 ) {
2650 gfx_new_line(im->canvas,
2651 im -> xorigin + ii, im->yorigin,
2652 im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2654 im -> gdes[i].col );
2655 } else if ( im -> gdes[i].yrule < 0 ) {
2656 gfx_new_line(im->canvas,
2657 im -> xorigin + ii, im->yorigin - im -> ysize,
2658 im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2660 im -> gdes[i].col );
2668 /* fix data points at oo and -oo */
2669 for(ii=0;ii<im->xsize;ii++){
2670 if (isinf(im->gdes[i].p_data[ii])){
2671 if (im->gdes[i].p_data[ii] > 0) {
2672 im->gdes[i].p_data[ii] = im->maxval ;
2674 im->gdes[i].p_data[ii] = im->minval ;
2680 /* *******************************************************
2685 -------|--t-1--t--------------------------------
2687 if we know the value at time t was a then
2688 we draw a square from t-1 to t with the value a.
2690 ********************************************************* */
2691 if (im->gdes[i].col != 0x0){
2692 /* GF_LINE and friend */
2693 if(im->gdes[i].gf == GF_LINE ){
2696 for(ii=1;ii<im->xsize;ii++){
2697 if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2701 if ( node == NULL ) {
2702 last_y = ytr(im,im->gdes[i].p_data[ii]);
2703 if ( im->slopemode == 0 ){
2704 node = gfx_new_line(im->canvas,
2705 ii-1+im->xorigin,last_y,
2706 ii+im->xorigin,last_y,
2707 im->gdes[i].linewidth,
2710 node = gfx_new_line(im->canvas,
2711 ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2712 ii+im->xorigin,last_y,
2713 im->gdes[i].linewidth,
2717 double new_y = ytr(im,im->gdes[i].p_data[ii]);
2718 if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2719 gfx_add_point(node,ii-1+im->xorigin,new_y);
2722 gfx_add_point(node,ii+im->xorigin,new_y);
2728 double *foreY=malloc(sizeof(double)*im->xsize*2);
2729 double *foreX=malloc(sizeof(double)*im->xsize*2);
2730 double *backY=malloc(sizeof(double)*im->xsize*2);
2731 double *backX=malloc(sizeof(double)*im->xsize*2);
2733 for(ii=0;ii<=im->xsize;ii++){
2735 if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2738 while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2739 node = gfx_new_area(im->canvas,
2742 foreX[cntI],foreY[cntI], im->gdes[i].col);
2743 while (cntI < idxI) {
2746 while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2747 gfx_add_point(node,foreX[cntI],foreY[cntI]);
2749 gfx_add_point(node,backX[idxI],backY[idxI]);
2753 while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;}
2754 gfx_add_point(node,backX[idxI],backY[idxI]);
2763 if (ii == im->xsize) break;
2765 /* keep things simple for now, just draw these bars
2766 do not try to build a big and complex area */
2769 if ( im->slopemode == 0 && ii==0){
2772 if ( isnan(im->gdes[i].p_data[ii]) ) {
2776 ytop = ytr(im,im->gdes[i].p_data[ii]);
2777 if ( lastgdes && im->gdes[i].stack ) {
2778 ybase = ytr(im,lastgdes->p_data[ii]);
2780 ybase = ytr(im,areazero);
2782 if ( ybase == ytop ){
2786 /* every area has to be wound clock-wise,
2787 so we have to make sur base remains base */
2789 double extra = ytop;
2793 if ( im->slopemode == 0 ){
2794 backY[++idxI] = ybase-0.2;
2795 backX[idxI] = ii+im->xorigin-1;
2796 foreY[idxI] = ytop+0.2;
2797 foreX[idxI] = ii+im->xorigin-1;
2799 backY[++idxI] = ybase-0.2;
2800 backX[idxI] = ii+im->xorigin;
2801 foreY[idxI] = ytop+0.2;
2802 foreX[idxI] = ii+im->xorigin;
2804 /* close up any remaining area */
2809 } /* else GF_LINE */
2810 } /* if color != 0x0 */
2811 /* make sure we do not run into trouble when stacking on NaN */
2812 for(ii=0;ii<im->xsize;ii++){
2813 if (isnan(im->gdes[i].p_data[ii])) {
2814 if (lastgdes && (im->gdes[i].stack)) {
2815 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2817 im->gdes[i].p_data[ii] = areazero;
2821 lastgdes = &(im->gdes[i]);
2823 #ifdef WITH_PIECHART
2825 if(isnan(im->gdes[i].yrule)) /* fetch variable */
2826 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2828 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
2829 pie_part(im,im->gdes[i].col,
2830 im->pie_x,im->pie_y,im->piesize*0.4,
2831 M_PI*2.0*PieStart/100.0,
2832 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2833 PieStart += im->gdes[i].yrule;
2838 rrd_set_error("STACK should already be turned into LINE or AREA here");
2844 #ifdef WITH_PIECHART
2852 /* grid_paint also does the text */
2853 if( !(im->extra_flags & ONLY_GRAPH) )
2857 if( !(im->extra_flags & ONLY_GRAPH) )
2860 /* the RULES are the last thing to paint ... */
2861 for(i=0;i<im->gdes_c;i++){
2863 switch(im->gdes[i].gf){
2865 if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2866 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2868 if(im->gdes[i].yrule >= im->minval
2869 && im->gdes[i].yrule <= im->maxval)
2870 gfx_new_line(im->canvas,
2871 im->xorigin,ytr(im,im->gdes[i].yrule),
2872 im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2873 1.0,im->gdes[i].col);
2876 if(im->gdes[i].xrule == 0) { /* fetch variable */
2877 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2879 if(im->gdes[i].xrule >= im->start
2880 && im->gdes[i].xrule <= im->end)
2881 gfx_new_line(im->canvas,
2882 xtr(im,im->gdes[i].xrule),im->yorigin,
2883 xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2884 1.0,im->gdes[i].col);
2892 if (strcmp(im->graphfile,"-")==0) {
2893 fo = im->graphhandle ? im->graphhandle : stdout;
2894 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2895 /* Change translation mode for stdout to BINARY */
2896 _setmode( _fileno( fo ), O_BINARY );
2899 if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2900 rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2901 rrd_strerror(errno));
2905 gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2906 if (strcmp(im->graphfile,"-") != 0)
2912 /*****************************************************
2914 *****************************************************/
2917 gdes_alloc(image_desc_t *im){
2920 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2921 * sizeof(graph_desc_t)))==NULL){
2922 rrd_set_error("realloc graph_descs");
2927 im->gdes[im->gdes_c-1].step=im->step;
2928 im->gdes[im->gdes_c-1].step_orig=im->step;
2929 im->gdes[im->gdes_c-1].stack=0;
2930 im->gdes[im->gdes_c-1].linewidth=0;
2931 im->gdes[im->gdes_c-1].debug=0;
2932 im->gdes[im->gdes_c-1].start=im->start;
2933 im->gdes[im->gdes_c-1].start_orig=im->start;
2934 im->gdes[im->gdes_c-1].end=im->end;
2935 im->gdes[im->gdes_c-1].end_orig=im->end;
2936 im->gdes[im->gdes_c-1].vname[0]='\0';
2937 im->gdes[im->gdes_c-1].data=NULL;
2938 im->gdes[im->gdes_c-1].ds_namv=NULL;
2939 im->gdes[im->gdes_c-1].data_first=0;
2940 im->gdes[im->gdes_c-1].p_data=NULL;
2941 im->gdes[im->gdes_c-1].rpnp=NULL;
2942 im->gdes[im->gdes_c-1].shift=0;
2943 im->gdes[im->gdes_c-1].col = 0x0;
2944 im->gdes[im->gdes_c-1].legend[0]='\0';
2945 im->gdes[im->gdes_c-1].format[0]='\0';
2946 im->gdes[im->gdes_c-1].rrd[0]='\0';
2947 im->gdes[im->gdes_c-1].ds=-1;
2948 im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;
2949 im->gdes[im->gdes_c-1].cf=CF_AVERAGE;
2950 im->gdes[im->gdes_c-1].p_data=NULL;
2951 im->gdes[im->gdes_c-1].yrule=DNAN;
2952 im->gdes[im->gdes_c-1].xrule=0;
2956 /* copies input untill the first unescaped colon is found
2957 or until input ends. backslashes have to be escaped as well */
2959 scan_for_col(const char *const input, int len, char *const output)
2964 input[inp] != ':' &&
2967 if (input[inp] == '\\' &&
2968 input[inp+1] != '\0' &&
2969 (input[inp+1] == '\\' ||
2970 input[inp+1] == ':')){
2971 output[outp++] = input[++inp];
2974 output[outp++] = input[inp];
2977 output[outp] = '\0';
2980 /* Some surgery done on this function, it became ridiculously big.
2982 ** - initializing now in rrd_graph_init()
2983 ** - options parsing now in rrd_graph_options()
2984 ** - script parsing now in rrd_graph_script()
2987 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
2990 rrd_graph_init(&im);
2991 im.graphhandle = stream;
2993 rrd_graph_options(argc,argv,&im);
2994 if (rrd_test_error()) {
2999 if (strlen(argv[optind])>=MAXPATH) {
3000 rrd_set_error("filename (including path) too long");
3004 strncpy(im.graphfile,argv[optind],MAXPATH-1);
3005 im.graphfile[MAXPATH-1]='\0';
3007 rrd_graph_script(argc,argv,&im,1);
3008 if (rrd_test_error()) {
3013 /* Everything is now read and the actual work can start */
3016 if (graph_paint(&im,prdata)==-1){
3021 /* The image is generated and needs to be output.
3022 ** Also, if needed, print a line with information about the image.
3032 /* maybe prdata is not allocated yet ... lets do it now */
3033 if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
3034 rrd_set_error("malloc imginfo");
3038 if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3040 rrd_set_error("malloc imginfo");
3043 filename=im.graphfile+strlen(im.graphfile);
3044 while(filename > im.graphfile) {
3045 if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3049 sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
3056 rrd_graph_init(image_desc_t *im)
3063 #ifdef HAVE_SETLOCALE
3064 setlocale(LC_TIME,"");
3065 #ifdef HAVE_MBSTOWCS
3066 setlocale(LC_CTYPE,"");
3072 im->xlab_user.minsec = -1;
3078 im->ylegend[0] = '\0';
3079 im->title[0] = '\0';
3080 im->watermark[0] = '\0';
3083 im->unitsexponent= 9999;
3086 im->viewfactor = 1.0;
3093 im->logarithmic = 0;
3094 im->ygridstep = DNAN;
3095 im->draw_x_grid = 1;
3096 im->draw_y_grid = 1;
3101 im->canvas = gfx_new_canvas();
3102 im->grid_dash_on = 1;
3103 im->grid_dash_off = 1;
3104 im->tabwidth = 40.0;
3106 for(i=0;i<DIM(graph_col);i++)
3107 im->graph_col[i]=graph_col[i];
3109 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3112 char rrd_win_default_font[1000];
3113 windir = getenv("windir");
3114 /* %windir% is something like D:\windows or C:\winnt */
3115 if (windir != NULL) {
3116 strncpy(rrd_win_default_font,windir,500);
3117 rrd_win_default_font[500] = '\0';
3118 strcat(rrd_win_default_font,"\\fonts\\");
3119 strcat(rrd_win_default_font,RRD_DEFAULT_FONT);
3120 for(i=0;i<DIM(text_prop);i++){
3121 strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3122 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3129 deffont = getenv("RRD_DEFAULT_FONT");
3130 if (deffont != NULL) {
3131 for(i=0;i<DIM(text_prop);i++){
3132 strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3133 text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3137 for(i=0;i<DIM(text_prop);i++){
3138 im->text_prop[i].size = text_prop[i].size;
3139 strcpy(im->text_prop[i].font,text_prop[i].font);
3144 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3147 char *parsetime_error = NULL;
3148 char scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3149 time_t start_tmp=0,end_tmp=0;
3151 struct rrd_time_value start_tv, end_tv;
3153 optind = 0; opterr = 0; /* initialize getopt */
3155 parsetime("end-24h", &start_tv);
3156 parsetime("now", &end_tv);
3158 /* defines for long options without a short equivalent. should be bytes,
3159 and may not collide with (the ASCII value of) short options */
3160 #define LONGOPT_UNITS_SI 255
3163 static struct option long_options[] =
3165 {"start", required_argument, 0, 's'},
3166 {"end", required_argument, 0, 'e'},
3167 {"x-grid", required_argument, 0, 'x'},
3168 {"y-grid", required_argument, 0, 'y'},
3169 {"vertical-label",required_argument,0,'v'},
3170 {"width", required_argument, 0, 'w'},
3171 {"height", required_argument, 0, 'h'},
3172 {"interlaced", no_argument, 0, 'i'},
3173 {"upper-limit",required_argument, 0, 'u'},
3174 {"lower-limit",required_argument, 0, 'l'},
3175 {"rigid", no_argument, 0, 'r'},
3176 {"base", required_argument, 0, 'b'},
3177 {"logarithmic",no_argument, 0, 'o'},
3178 {"color", required_argument, 0, 'c'},
3179 {"font", required_argument, 0, 'n'},
3180 {"title", required_argument, 0, 't'},
3181 {"imginfo", required_argument, 0, 'f'},
3182 {"imgformat", required_argument, 0, 'a'},
3183 {"lazy", no_argument, 0, 'z'},
3184 {"zoom", required_argument, 0, 'm'},
3185 {"no-legend", no_argument, 0, 'g'},
3186 {"force-rules-legend",no_argument,0, 'F'},
3187 {"only-graph", no_argument, 0, 'j'},
3188 {"alt-y-grid", no_argument, 0, 'Y'},
3189 {"no-minor", no_argument, 0, 'I'},
3190 {"slope-mode", no_argument, 0, 'E'},
3191 {"alt-autoscale", no_argument, 0, 'A'},
3192 {"alt-autoscale-max", no_argument, 0, 'M'},
3193 {"no-gridfit", no_argument, 0, 'N'},
3194 {"units-exponent",required_argument, 0, 'X'},
3195 {"units-length",required_argument, 0, 'L'},
3196 {"units", required_argument, 0, LONGOPT_UNITS_SI },
3197 {"step", required_argument, 0, 'S'},
3198 {"tabwidth", required_argument, 0, 'T'},
3199 {"font-render-mode", required_argument, 0, 'R'},
3200 {"font-smoothing-threshold", required_argument, 0, 'B'},
3201 {"watermark", required_argument, 0, 'W'},
3202 {"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 */
3204 int option_index = 0;
3206 int col_start,col_end;
3208 opt = getopt_long(argc, argv,
3209 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3210 long_options, &option_index);
3217 im->extra_flags |= NOMINOR;
3220 im->extra_flags |= ALTYGRID;
3223 im->extra_flags |= ALTAUTOSCALE;
3226 im->extra_flags |= ALTAUTOSCALE_MAX;
3229 im->extra_flags |= ONLY_GRAPH;
3232 im->extra_flags |= NOLEGEND;
3235 im->extra_flags |= FORCE_RULES_LEGEND;
3237 case LONGOPT_UNITS_SI:
3238 if(im->extra_flags & FORCE_UNITS) {
3239 rrd_set_error("--units can only be used once!");
3242 if(strcmp(optarg,"si")==0)
3243 im->extra_flags |= FORCE_UNITS_SI;
3245 rrd_set_error("invalid argument for --units: %s", optarg );
3250 im->unitsexponent = atoi(optarg);
3253 im->unitslength = atoi(optarg);
3256 im->tabwidth = atof(optarg);
3259 im->step = atoi(optarg);
3265 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3266 rrd_set_error( "start time: %s", parsetime_error );
3271 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3272 rrd_set_error( "end time: %s", parsetime_error );
3277 if(strcmp(optarg,"none") == 0){
3283 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3285 &im->xlab_user.gridst,
3287 &im->xlab_user.mgridst,
3289 &im->xlab_user.labst,
3290 &im->xlab_user.precis,
3291 &stroff) == 7 && stroff != 0){
3292 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3293 im->xlab_form[sizeof(im->xlab_form)-1] = '\0';
3294 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3295 rrd_set_error("unknown keyword %s",scan_gtm);
3297 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3298 rrd_set_error("unknown keyword %s",scan_mtm);
3300 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3301 rrd_set_error("unknown keyword %s",scan_ltm);
3304 im->xlab_user.minsec = 1;
3305 im->xlab_user.stst = im->xlab_form;
3307 rrd_set_error("invalid x-grid format");
3313 if(strcmp(optarg,"none") == 0){
3321 &im->ylabfact) == 2) {
3322 if(im->ygridstep<=0){
3323 rrd_set_error("grid step must be > 0");
3325 } else if (im->ylabfact < 1){
3326 rrd_set_error("label factor must be > 0");
3330 rrd_set_error("invalid y-grid format");
3335 strncpy(im->ylegend,optarg,150);
3336 im->ylegend[150]='\0';
3339 im->maxval = atof(optarg);
3342 im->minval = atof(optarg);
3345 im->base = atol(optarg);
3346 if(im->base != 1024 && im->base != 1000 ){
3347 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3352 long_tmp = atol(optarg);
3353 if (long_tmp < 10) {
3354 rrd_set_error("width below 10 pixels");
3357 im->xsize = long_tmp;
3360 long_tmp = atol(optarg);
3361 if (long_tmp < 10) {
3362 rrd_set_error("height below 10 pixels");
3365 im->ysize = long_tmp;
3368 im->canvas->interlaced = 1;
3374 im->imginfo = optarg;
3377 if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3378 rrd_set_error("unsupported graphics format '%s'",optarg);
3390 im->logarithmic = 1;
3394 "%10[A-Z]#%n%8lx%n",
3395 col_nam,&col_start,&color,&col_end) == 2){
3397 int col_len = col_end - col_start;
3401 ((color & 0xF00) * 0x110000) |
3402 ((color & 0x0F0) * 0x011000) |
3403 ((color & 0x00F) * 0x001100) |
3409 ((color & 0xF000) * 0x11000) |
3410 ((color & 0x0F00) * 0x01100) |
3411 ((color & 0x00F0) * 0x00110) |
3412 ((color & 0x000F) * 0x00011)
3416 color = (color << 8) + 0xff /* shift left by 8 */;
3421 rrd_set_error("the color format is #RRGGBB[AA]");
3424 if((ci=grc_conv(col_nam)) != -1){
3425 im->graph_col[ci]=color;
3427 rrd_set_error("invalid color name '%s'",col_nam);
3431 rrd_set_error("invalid color def format");
3438 char font[1024] = "";
3441 "%10[A-Z]:%lf:%1000s",
3442 prop,&size,font) >= 2){
3444 if((sindex=text_prop_conv(prop)) != -1){
3445 for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){
3447 im->text_prop[propidx].size=size;
3449 if (strlen(font) > 0){
3450 strcpy(im->text_prop[propidx].font,font);
3452 if (propidx==sindex && sindex != 0) break;
3455 rrd_set_error("invalid fonttag '%s'",prop);
3459 rrd_set_error("invalid text property format");
3465 im->canvas->zoom = atof(optarg);
3466 if (im->canvas->zoom <= 0.0) {
3467 rrd_set_error("zoom factor must be > 0");
3472 strncpy(im->title,optarg,150);
3473 im->title[150]='\0';
3477 if ( strcmp( optarg, "normal" ) == 0 )
3478 im->canvas->aa_type = AA_NORMAL;
3479 else if ( strcmp( optarg, "light" ) == 0 )
3480 im->canvas->aa_type = AA_LIGHT;
3481 else if ( strcmp( optarg, "mono" ) == 0 )
3482 im->canvas->aa_type = AA_NONE;
3485 rrd_set_error("unknown font-render-mode '%s'", optarg );
3491 im->canvas->font_aa_threshold = atof(optarg);
3495 strncpy(im->watermark,optarg,100);
3496 im->watermark[99]='\0';
3501 rrd_set_error("unknown option '%c'", optopt);
3503 rrd_set_error("unknown option '%s'",argv[optind-1]);
3508 if (optind >= argc) {
3509 rrd_set_error("missing filename");
3513 if (im->logarithmic == 1 && im->minval <= 0){
3514 rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");
3518 if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3519 /* error string is set in parsetime.c */
3523 if (start_tmp < 3600*24*365*10){
3524 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3528 if (end_tmp < start_tmp) {
3529 rrd_set_error("start (%ld) should be less than end (%ld)",
3530 start_tmp, end_tmp);
3534 im->start = start_tmp;
3536 im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3540 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3542 if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3543 rrd_set_error("Unknown variable '%s' in %s",varname,err);
3549 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3552 graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3554 color=strstr(var,"#");
3557 rrd_set_error("Found no color in %s",err);
3566 rest=strstr(color,":");
3574 sscanf(color,"#%6lx%n",&col,&n);
3575 col = (col << 8) + 0xff /* shift left by 8 */;
3576 if (n!=7) rrd_set_error("Color problem in %s",err);
3579 sscanf(color,"#%8lx%n",&col,&n);
3582 rrd_set_error("Color problem in %s",err);
3584 if (rrd_test_error()) return 0;
3591 int bad_format(char *fmt) {
3595 while (*ptr != '\0')
3596 if (*ptr++ == '%') {
3598 /* line cannot end with percent char */
3599 if (*ptr == '\0') return 1;
3601 /* '%s', '%S' and '%%' are allowed */
3602 if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3604 /* %c is allowed (but use only with vdef!) */
3605 else if (*ptr == 'c') {
3610 /* or else '% 6.2lf' and such are allowed */
3612 /* optional padding character */
3613 if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3615 /* This should take care of 'm.n' with all three optional */
3616 while (*ptr >= '0' && *ptr <= '9') ptr++;
3617 if (*ptr == '.') ptr++;
3618 while (*ptr >= '0' && *ptr <= '9') ptr++;
3620 /* Either 'le', 'lf' or 'lg' must follow here */
3621 if (*ptr++ != 'l') return 1;
3622 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3633 vdef_parse(gdes,str)
3634 struct graph_desc_t *gdes;
3635 const char *const str;
3637 /* A VDEF currently is either "func" or "param,func"
3638 * so the parsing is rather simple. Change if needed.
3645 sscanf(str,"%le,%29[A-Z]%n",¶m,func,&n);
3646 if (n== (int)strlen(str)) { /* matched */
3650 sscanf(str,"%29[A-Z]%n",func,&n);
3651 if (n== (int)strlen(str)) { /* matched */
3654 rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3661 if (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3662 else if (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3663 else if (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3664 else if (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3665 else if (!strcmp("TOTAL", func)) gdes->vf.op = VDEF_TOTAL;
3666 else if (!strcmp("FIRST", func)) gdes->vf.op = VDEF_FIRST;
3667 else if (!strcmp("LAST", func)) gdes->vf.op = VDEF_LAST;
3668 else if (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3669 else if (!strcmp("LSLINT", func)) gdes->vf.op = VDEF_LSLINT;
3670 else if (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3672 rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3679 switch (gdes->vf.op) {
3681 if (isnan(param)) { /* no parameter given */
3682 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3688 if (param>=0.0 && param<=100.0) {
3689 gdes->vf.param = param;
3690 gdes->vf.val = DNAN; /* undefined */
3691 gdes->vf.when = 0; /* undefined */
3693 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3708 case VDEF_LSLCORREL:
3710 gdes->vf.param = DNAN;
3711 gdes->vf.val = DNAN;
3714 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3731 graph_desc_t *src,*dst;
3735 dst = &im->gdes[gdi];
3736 src = &im->gdes[dst->vidx];
3737 data = src->data + src->ds;
3738 steps = (src->end - src->start) / src->step;
3741 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3748 switch (dst->vf.op) {
3749 case VDEF_PERCENT: {
3750 rrd_value_t * array;
3754 if ((array = malloc(steps*sizeof(double)))==NULL) {
3755 rrd_set_error("malloc VDEV_PERCENT");
3758 for (step=0;step < steps; step++) {
3759 array[step]=data[step*src->ds_cnt];
3761 qsort(array,step,sizeof(double),vdef_percent_compar);
3763 field = (steps-1)*dst->vf.param/100;
3764 dst->vf.val = array[field];
3765 dst->vf.when = 0; /* no time component */
3768 for(step=0;step<steps;step++)
3769 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3775 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3776 if (step == steps) {
3780 dst->vf.val = data[step*src->ds_cnt];
3781 dst->vf.when = src->start + (step+1)*src->step;
3783 while (step != steps) {
3784 if (finite(data[step*src->ds_cnt])) {
3785 if (data[step*src->ds_cnt] > dst->vf.val) {
3786 dst->vf.val = data[step*src->ds_cnt];
3787 dst->vf.when = src->start + (step+1)*src->step;
3794 case VDEF_AVERAGE: {
3797 for (step=0;step<steps;step++) {
3798 if (finite(data[step*src->ds_cnt])) {
3799 sum += data[step*src->ds_cnt];
3804 if (dst->vf.op == VDEF_TOTAL) {
3805 dst->vf.val = sum*src->step;
3806 dst->vf.when = cnt*src->step; /* not really "when" */
3808 dst->vf.val = sum/cnt;
3809 dst->vf.when = 0; /* no time component */
3819 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3820 if (step == steps) {
3824 dst->vf.val = data[step*src->ds_cnt];
3825 dst->vf.when = src->start + (step+1)*src->step;
3827 while (step != steps) {
3828 if (finite(data[step*src->ds_cnt])) {
3829 if (data[step*src->ds_cnt] < dst->vf.val) {
3830 dst->vf.val = data[step*src->ds_cnt];
3831 dst->vf.when = src->start + (step+1)*src->step;
3838 /* The time value returned here is one step before the
3839 * actual time value. This is the start of the first
3843 while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3844 if (step == steps) { /* all entries were NaN */
3848 dst->vf.val = data[step*src->ds_cnt];
3849 dst->vf.when = src->start + step*src->step;
3853 /* The time value returned here is the
3854 * actual time value. This is the end of the last
3858 while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3859 if (step < 0) { /* all entries were NaN */
3863 dst->vf.val = data[step*src->ds_cnt];
3864 dst->vf.when = src->start + (step+1)*src->step;
3869 case VDEF_LSLCORREL:{
3870 /* Bestfit line by linear least squares method */
3873 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3874 SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3876 for (step=0;step<steps;step++) {
3877 if (finite(data[step*src->ds_cnt])) {
3880 SUMxx += step * step;
3881 SUMxy += step * data[step*src->ds_cnt];
3882 SUMy += data[step*src->ds_cnt];
3883 SUMyy += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3887 slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3888 y_intercept = ( SUMy - slope*SUMx ) / cnt;
3889 correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3892 if (dst->vf.op == VDEF_LSLSLOPE) {
3893 dst->vf.val = slope;
3894 dst->vf.when = cnt*src->step;
3895 } else if (dst->vf.op == VDEF_LSLINT) {
3896 dst->vf.val = y_intercept;
3897 dst->vf.when = cnt*src->step;
3898 } else if (dst->vf.op == VDEF_LSLCORREL) {
3899 dst->vf.val = correl;
3900 dst->vf.when = cnt*src->step;
3913 /* NaN < -INF < finite_values < INF */
3915 vdef_percent_compar(a,b)
3918 /* Equality is not returned; this doesn't hurt except
3919 * (maybe) for a little performance.
3922 /* First catch NaN values. They are smallest */
3923 if (isnan( *(double *)a )) return -1;
3924 if (isnan( *(double *)b )) return 1;
3926 /* NaN doesn't reach this part so INF and -INF are extremes.
3927 * The sign from isinf() is compatible with the sign we return
3929 if (isinf( *(double *)a )) return isinf( *(double *)a );
3930 if (isinf( *(double *)b )) return isinf( *(double *)b );
3932 /* If we reach this, both values must be finite */
3933 if ( *(double *)a < *(double *)b ) return -1; else return 1;