Code

the end time itself is not part of a time range ... this fixes the segfault reported...
[rrdtool.git] / src / rrd_graph.c
1 /****************************************************************************
2  * RRDtool 1.2.30  Copyright by Tobi Oetiker, 1997-2009
3  ****************************************************************************
4  * rrd__graph.c  produce graphs from data in rrdfiles
5  ****************************************************************************/
8 #include <sys/stat.h>
10 #ifdef WIN32
11 #include "strftime.h"
12 #endif
13 #include "rrd_tool.h"
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
16 #include <io.h>
17 #include <fcntl.h>
18 #endif
20 #ifdef HAVE_TIME_H
21 #include <time.h>
22 #endif
24 #ifdef HAVE_LOCALE_H
25 #include <locale.h>
26 #endif
28 #include "rrd_graph.h"
30 /* some constant definitions */
34 #ifndef RRD_DEFAULT_FONT
35 /* there is special code later to pick Cour.ttf when running on windows */
36 #define RRD_DEFAULT_FONT "DejaVuSansMono-Roman.ttf"
37 #endif
39 text_prop_t text_prop[] = {   
40      { 8.0, RRD_DEFAULT_FONT }, /* default */
41      { 9.0, RRD_DEFAULT_FONT }, /* title */
42      { 7.0,  RRD_DEFAULT_FONT }, /* axis */
43      { 8.0, RRD_DEFAULT_FONT }, /* unit */
44      { 8.0, RRD_DEFAULT_FONT }  /* legend */
45 };
47 xlab_t xlab[] = {
48     {0,                 0,   TMT_SECOND,30, TMT_MINUTE,5,  TMT_MINUTE,5,         0,"%H:%M"},
49     {2,                 0,   TMT_MINUTE,1,  TMT_MINUTE,5,  TMT_MINUTE,5,         0,"%H:%M"},
50     {5,                 0,   TMT_MINUTE,2,  TMT_MINUTE,10, TMT_MINUTE,10,        0,"%H:%M"},
51     {10,                0,   TMT_MINUTE,5,  TMT_MINUTE,20, TMT_MINUTE,20,        0,"%H:%M"},
52     {30,                0,   TMT_MINUTE,10, TMT_HOUR,1,    TMT_HOUR,1,           0,"%H:%M"},
53     {60,                0,   TMT_MINUTE,30, TMT_HOUR,2,    TMT_HOUR,2,           0,"%H:%M"},
54     {60,          24*3600,   TMT_MINUTE,30, TMT_HOUR,2,    TMT_HOUR,6,           0,"%a %H:%M"},
55     {180,               0,   TMT_HOUR,1,    TMT_HOUR,6,    TMT_HOUR,6,           0,"%H:%M"},
56     {180,         24*3600,   TMT_HOUR,1,    TMT_HOUR,6,    TMT_HOUR,12,          0,"%a %H:%M"},
57     /*{300,             0,   TMT_HOUR,3,    TMT_HOUR,12,   TMT_HOUR,12,    12*3600,"%a %p"},  this looks silly*/
58     {600,               0,   TMT_HOUR,6,    TMT_DAY,1,     TMT_DAY,1,      24*3600,"%a"},
59     {1200,               0,   TMT_HOUR,6,    TMT_DAY,1,     TMT_DAY,1,      24*3600,"%d"},
60     {1800,              0,   TMT_HOUR,12,   TMT_DAY,1,     TMT_DAY,2,      24*3600,"%a %d"},
61     {2400,              0,   TMT_HOUR,12,   TMT_DAY,1,     TMT_DAY,2,      24*3600,"%a"},
62     {3600,              0,   TMT_DAY,1,     TMT_WEEK,1,    TMT_WEEK,1,   7*24*3600,"Week %V"},
63     {3*3600,            0,   TMT_WEEK,1,    TMT_MONTH,1,   TMT_WEEK,2,   7*24*3600,"Week %V"},
64     {6*3600,            0,   TMT_MONTH,1,   TMT_MONTH,1,   TMT_MONTH,1, 30*24*3600,"%b"},
65     {48*3600,           0,   TMT_MONTH,1,   TMT_MONTH,3,   TMT_MONTH,3, 30*24*3600,"%b"},
66     {315360,            0,   TMT_MONTH,3,   TMT_YEAR,1,    TMT_YEAR,1,  365*24*3600,"%Y"},
67     {10*24*3600,        0,   TMT_YEAR,1,  TMT_YEAR,1,    TMT_YEAR,1, 365*24*3600,"%y"},
68     {-1,0,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
69 };
71 /* sensible y label intervals ...*/
73 ylab_t ylab[]= {
74     {0.1, {1,2, 5,10}},
75     {0.2, {1,5,10,20}},
76     {0.5, {1,2, 4,10}},
77     {1.0,   {1,2, 5,10}},
78     {2.0,   {1,5,10,20}},
79     {5.0,   {1,2, 4,10}},
80     {10.0,  {1,2, 5,10}},
81     {20.0,  {1,5,10,20}},
82     {50.0,  {1,2, 4,10}},
83     {100.0, {1,2, 5,10}},
84     {200.0, {1,5,10,20}},
85     {500.0, {1,2, 4,10}},
86     {0.0,   {0,0,0,0}}};
89 gfx_color_t graph_col[] =   /* default colors */
90 {    0xFFFFFFFF,   /* canvas     */
91      0xF0F0F0FF,   /* background */
92      0xD0D0D0FF,   /* shade A    */
93      0xA0A0A0FF,   /* shade B    */
94      0x90909080,   /* grid       */
95      0xE0505080,   /* major grid */
96      0x000000FF,   /* font       */ 
97      0x802020FF,   /* arrow      */
98      0x202020FF,   /* axis       */
99      0x000000FF    /* frame      */ 
100 };     
103 /* #define DEBUG */
105 #ifdef DEBUG
106 # define DPRINT(x)    (void)(printf x, printf("\n"))
107 #else
108 # define DPRINT(x)
109 #endif
112 /* initialize with xtr(im,0); */
113 int
114 xtr(image_desc_t *im,time_t mytime){
115     static double pixie;
116     if (mytime==0){
117         pixie = (double) im->xsize / (double)(im->end - im->start);
118         return im->xorigin;
119     }
120     return (int)((double)im->xorigin 
121                  + pixie * ( mytime - im->start ) );
124 /* translate data values into y coordinates */
125 double
126 ytr(image_desc_t *im, double value){
127     static double pixie;
128     double yval;
129     if (isnan(value)){
130       if(!im->logarithmic)
131         pixie = (double) im->ysize / (im->maxval - im->minval);
132       else 
133         pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
134       yval = im->yorigin;
135     } else if(!im->logarithmic) {
136       yval = im->yorigin - pixie * (value - im->minval);
137     } else {
138       if (value < im->minval) {
139         yval = im->yorigin;
140       } else {
141         yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
142       }
143     }
144     /* make sure we don't return anything too unreasonable. GD lib can
145        get terribly slow when drawing lines outside its scope. This is 
146        especially problematic in connection with the rigid option */
147     if (! im->rigid) {
148       /* keep yval as-is */
149     } else if (yval > im->yorigin) {
150       yval = im->yorigin +0.00001;
151     } else if (yval < im->yorigin - im->ysize){
152       yval = im->yorigin - im->ysize - 0.00001;
153     } 
154     return yval;
159 /* conversion function for symbolic entry names */
162 #define conv_if(VV,VVV) \
163    if (strcmp(#VV, string) == 0) return VVV ;
165 enum gf_en gf_conv(char *string){
166     
167     conv_if(PRINT,GF_PRINT)
168     conv_if(GPRINT,GF_GPRINT)
169     conv_if(COMMENT,GF_COMMENT)
170     conv_if(HRULE,GF_HRULE)
171     conv_if(VRULE,GF_VRULE)
172     conv_if(LINE,GF_LINE)
173     conv_if(AREA,GF_AREA)
174     conv_if(STACK,GF_STACK) 
175     conv_if(TICK,GF_TICK)
176     conv_if(DEF,GF_DEF)
177     conv_if(CDEF,GF_CDEF)
178     conv_if(VDEF,GF_VDEF)
179 #ifdef WITH_PIECHART
180     conv_if(PART,GF_PART)
181 #endif
182     conv_if(XPORT,GF_XPORT)
183     conv_if(SHIFT,GF_SHIFT)
184     
185     return (-1);
188 enum gfx_if_en if_conv(char *string){
189     
190     conv_if(PNG,IF_PNG)
191     conv_if(SVG,IF_SVG)
192     conv_if(EPS,IF_EPS)
193     conv_if(PDF,IF_PDF)
195     return (-1);
198 enum tmt_en tmt_conv(char *string){
200     conv_if(SECOND,TMT_SECOND)
201     conv_if(MINUTE,TMT_MINUTE)
202     conv_if(HOUR,TMT_HOUR)
203     conv_if(DAY,TMT_DAY)
204     conv_if(WEEK,TMT_WEEK)
205     conv_if(MONTH,TMT_MONTH)
206     conv_if(YEAR,TMT_YEAR)
207     return (-1);
210 enum grc_en grc_conv(char *string){
212     conv_if(BACK,GRC_BACK)
213     conv_if(CANVAS,GRC_CANVAS)
214     conv_if(SHADEA,GRC_SHADEA)
215     conv_if(SHADEB,GRC_SHADEB)
216     conv_if(GRID,GRC_GRID)
217     conv_if(MGRID,GRC_MGRID)
218     conv_if(FONT,GRC_FONT)
219     conv_if(ARROW,GRC_ARROW)
220     conv_if(AXIS,GRC_AXIS)
221     conv_if(FRAME,GRC_FRAME)
223     return -1;        
226 enum text_prop_en text_prop_conv(char *string){
227       
228     conv_if(DEFAULT,TEXT_PROP_DEFAULT)
229     conv_if(TITLE,TEXT_PROP_TITLE)
230     conv_if(AXIS,TEXT_PROP_AXIS)
231     conv_if(UNIT,TEXT_PROP_UNIT)
232     conv_if(LEGEND,TEXT_PROP_LEGEND)
233     return -1;
237 #undef conv_if
239 int
240 im_free(image_desc_t *im)
242     unsigned long        i,ii;
244     if (im == NULL) return 0;
245     for(i=0;i<(unsigned)im->gdes_c;i++){
246       if (im->gdes[i].data_first){
247         /* careful here, because a single pointer can occur several times */
248           free (im->gdes[i].data);
249           if (im->gdes[i].ds_namv){
250               for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
251                   free(im->gdes[i].ds_namv[ii]);
252               free(im->gdes[i].ds_namv);
253           }
254       }
255       free (im->gdes[i].p_data);
256       free (im->gdes[i].rpnp);
257     }
258     free(im->gdes);
259     gfx_destroy(im->canvas);
260     return 0;
263 /* find SI magnitude symbol for the given number*/
264 void
265 auto_scale(
266            image_desc_t *im,   /* image description */
267            double *value,
268            char **symb_ptr,
269            double *magfact
270            )
272         
273     char *symbol[] = {"a", /* 10e-18 Atto */
274                       "f", /* 10e-15 Femto */
275                       "p", /* 10e-12 Pico */
276                       "n", /* 10e-9  Nano */
277                       "u", /* 10e-6  Micro */
278                       "m", /* 10e-3  Milli */
279                       " ", /* Base */
280                       "k", /* 10e3   Kilo */
281                       "M", /* 10e6   Mega */
282                       "G", /* 10e9   Giga */
283                       "T", /* 10e12  Tera */
284                       "P", /* 10e15  Peta */
285                       "E"};/* 10e18  Exa */
287     int symbcenter = 6;
288     int sindex;  
290     if (*value == 0.0 || isnan(*value) ) {
291         sindex = 0;
292         *magfact = 1.0;
293     } else {
294         sindex = floor(log(fabs(*value))/log((double)im->base)); 
295         *magfact = pow((double)im->base, (double)sindex);
296         (*value) /= (*magfact);
297     }
298     if ( sindex <= symbcenter && sindex >= -symbcenter) {
299         (*symb_ptr) = symbol[sindex+symbcenter];
300     }
301     else {
302         (*symb_ptr) = "?";
303     }
307 static char si_symbol[] = {
308                      'a', /* 10e-18 Atto */ 
309                      'f', /* 10e-15 Femto */
310                      'p', /* 10e-12 Pico */
311                      'n', /* 10e-9  Nano */
312                      'u', /* 10e-6  Micro */
313                      'm', /* 10e-3  Milli */
314                      ' ', /* Base */
315                      'k', /* 10e3   Kilo */
316                      'M', /* 10e6   Mega */
317                      'G', /* 10e9   Giga */
318                      'T', /* 10e12  Tera */
319                      'P', /* 10e15  Peta */
320                      'E', /* 10e18  Exa */
321 };
322 static const int si_symbcenter = 6;
324 /* find SI magnitude symbol for the numbers on the y-axis*/
325 void 
326 si_unit(
327     image_desc_t *im   /* image description */
331     double digits,viewdigits=0;  
332     
333     digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base)); 
335     if (im->unitsexponent != 9999) {
336         /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
337         viewdigits = floor(im->unitsexponent / 3);
338     } else {
339         viewdigits = digits;
340     }
342     im->magfact = pow((double)im->base , digits);
343     
344 #ifdef DEBUG
345     printf("digits %6.3f  im->magfact %6.3f\n",digits,im->magfact);
346 #endif
348     im->viewfactor = im->magfact / pow((double)im->base , viewdigits);
350     if ( ((viewdigits+si_symbcenter) < sizeof(si_symbol)) &&
351                     ((viewdigits+si_symbcenter) >= 0) )
352         im->symbol = si_symbol[(int)viewdigits+si_symbcenter];
353     else
354         im->symbol = '?';
355  }
357 /*  move min and max values around to become sensible */
359 void 
360 expand_range(image_desc_t *im)
362     double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
363                               600.0,500.0,400.0,300.0,250.0,
364                               200.0,125.0,100.0,90.0,80.0,
365                               75.0,70.0,60.0,50.0,40.0,30.0,
366                               25.0,20.0,10.0,9.0,8.0,
367                               7.0,6.0,5.0,4.0,3.5,3.0,
368                               2.5,2.0,1.8,1.5,1.2,1.0,
369                               0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
370     
371     double scaled_min,scaled_max;  
372     double adj;
373     int i;
374     
376     
377 #ifdef DEBUG
378     printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
379            im->minval,im->maxval,im->magfact);
380 #endif
382     if (isnan(im->ygridstep)){
383         if(im->extra_flags & ALTAUTOSCALE) {
384             /* measure the amplitude of the function. Make sure that
385                graph boundaries are slightly higher then max/min vals
386                so we can see amplitude on the graph */
387               double delt, fact;
389               delt = im->maxval - im->minval;
390               adj = delt * 0.1;
391               fact = 2.0 * pow(10.0,
392                     floor(log10(max(fabs(im->minval), fabs(im->maxval))/im->magfact)) - 2);
393               if (delt < fact) {
394                 adj = (fact - delt) * 0.55;
395 #ifdef DEBUG
396               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 #endif
398               }
399               im->minval -= adj;
400               im->maxval += adj;
401         }
402         else if(im->extra_flags & ALTAUTOSCALE_MIN) {
403             /* measure the amplitude of the function. Make sure that
404                graph boundaries are slightly lower than min vals
405                so we can see amplitude on the graph */
406               adj = (im->maxval - im->minval) * 0.1;
407               im->minval -= adj;
408         }
409         else if(im->extra_flags & ALTAUTOSCALE_MAX) {
410             /* measure the amplitude of the function. Make sure that
411                graph boundaries are slightly higher than max vals
412                so we can see amplitude on the graph */
413               adj = (im->maxval - im->minval) * 0.1;
414               im->maxval += adj;
415         }
416         else {
417             scaled_min = im->minval / im->magfact;
418             scaled_max = im->maxval / im->magfact;
419             
420             for (i=1; sensiblevalues[i] > 0; i++){
421                 if (sensiblevalues[i-1]>=scaled_min &&
422                     sensiblevalues[i]<=scaled_min)        
423                     im->minval = sensiblevalues[i]*(im->magfact);
424                 
425                 if (-sensiblevalues[i-1]<=scaled_min &&
426                     -sensiblevalues[i]>=scaled_min)
427                     im->minval = -sensiblevalues[i-1]*(im->magfact);
428                 
429                 if (sensiblevalues[i-1] >= scaled_max &&
430                     sensiblevalues[i] <= scaled_max)
431                     im->maxval = sensiblevalues[i-1]*(im->magfact);
432                 
433                 if (-sensiblevalues[i-1]<=scaled_max &&
434                     -sensiblevalues[i] >=scaled_max)
435                     im->maxval = -sensiblevalues[i]*(im->magfact);
436             }
437         }
438     } else {
439         /* adjust min and max to the grid definition if there is one */
440         im->minval = (double)im->ylabfact * im->ygridstep * 
441             floor(im->minval / ((double)im->ylabfact * im->ygridstep));
442         im->maxval = (double)im->ylabfact * im->ygridstep * 
443             ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
444     }
445     
446 #ifdef DEBUG
447     fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
448            im->minval,im->maxval,im->magfact);
449 #endif
452 void
453 apply_gridfit(image_desc_t *im)
455   if (isnan(im->minval) || isnan(im->maxval))
456     return;
457   ytr(im,DNAN);
458   if (im->logarithmic) {
459     double ya, yb, ypix, ypixfrac;
460     double log10_range = log10(im->maxval) - log10(im->minval);
461     ya = pow((double)10, floor(log10(im->minval)));
462     while (ya < im->minval)
463       ya *= 10;
464     if (ya > im->maxval)
465       return; /* don't have y=10^x gridline */
466     yb = ya * 10;
467     if (yb <= im->maxval) {
468       /* we have at least 2 y=10^x gridlines.
469          Make sure distance between them in pixels
470          are an integer by expanding im->maxval */
471       double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
472       double factor = y_pixel_delta / floor(y_pixel_delta);
473       double new_log10_range = factor * log10_range;
474       double new_ymax_log10 = log10(im->minval) + new_log10_range;
475       im->maxval = pow(10, new_ymax_log10);
476       ytr(im,DNAN); /* reset precalc */
477       log10_range = log10(im->maxval) - log10(im->minval);
478     }
479     /* make sure first y=10^x gridline is located on 
480        integer pixel position by moving scale slightly 
481        downwards (sub-pixel movement) */
482     ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
483     ypixfrac = ypix - floor(ypix);
484     if (ypixfrac > 0 && ypixfrac < 1) {
485       double yfrac = ypixfrac / im->ysize;
486       im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
487       im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
488       ytr(im,DNAN); /* reset precalc */
489     }
490   } else {
491     /* Make sure we have an integer pixel distance between
492        each minor gridline */
493     double ypos1 = ytr(im, im->minval);
494     double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
495     double y_pixel_delta = ypos1 - ypos2;
496     double factor = y_pixel_delta / floor(y_pixel_delta);
497     double new_range = factor * (im->maxval - im->minval);
498     double gridstep = im->ygrid_scale.gridstep;
499     double minor_y, minor_y_px, minor_y_px_frac;
502     if (im->maxval > 0.0)
503       im->maxval = im->minval + new_range;
504     else
505       im->minval = im->maxval - new_range;
506     ytr(im,DNAN); /* reset precalc */
508     /* make sure first minor gridline is on integer pixel y coord */
509     minor_y = gridstep * floor(im->minval / gridstep);
510     while (minor_y < im->minval)
511       minor_y += gridstep;
512     minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
513     minor_y_px_frac = minor_y_px - floor(minor_y_px);
514     if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
515       double yfrac = minor_y_px_frac / im->ysize;
516       double range = im->maxval - im->minval;
517       im->minval = im->minval - yfrac * range;
518       im->maxval = im->maxval - yfrac * range;
519       ytr(im,DNAN); /* reset precalc */
520     }
521     calc_horizontal_grid(im); /* recalc with changed im->maxval */
522   }
525 /* reduce data reimplementation by Alex */
527 void
528 reduce_data(
529     enum cf_en     cf,         /* which consolidation function ?*/
530     unsigned long  cur_step,   /* step the data currently is in */
531     time_t         *start,     /* start, end and step as requested ... */
532     time_t         *end,       /* ... by the application will be   ... */
533     unsigned long  *step,      /* ... adjusted to represent reality    */
534     unsigned long  *ds_cnt,    /* number of data sources in file */
535     rrd_value_t    **data)     /* two dimensional array containing the data */
537     int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
538     unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
539     rrd_value_t    *srcptr,*dstptr;
541     (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
542     dstptr = *data;
543     srcptr = *data;
544     row_cnt = ((*end)-(*start))/cur_step;
546 #ifdef DEBUG
547 #define DEBUG_REDUCE
548 #endif
549 #ifdef DEBUG_REDUCE
550 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
551                         row_cnt,reduce_factor,*start,*end,cur_step);
552 for (col=0;col<row_cnt;col++) {
553     printf("time %10lu: ",*start+(col+1)*cur_step);
554     for (i=0;i<*ds_cnt;i++)
555         printf(" %8.2e",srcptr[*ds_cnt*col+i]);
556     printf("\n");
558 #endif
560     /* We have to combine [reduce_factor] rows of the source
561     ** into one row for the destination.  Doing this we also
562     ** need to take care to combine the correct rows.  First
563     ** alter the start and end time so that they are multiples
564     ** of the new step time.  We cannot reduce the amount of
565     ** time so we have to move the end towards the future and
566     ** the start towards the past.
567     */
568     end_offset = (*end) % (*step);
569     start_offset = (*start) % (*step);
571     /* If there is a start offset (which cannot be more than
572     ** one destination row), skip the appropriate number of
573     ** source rows and one destination row.  The appropriate
574     ** number is what we do know (start_offset/cur_step) of
575     ** the new interval (*step/cur_step aka reduce_factor).
576     */
577 #ifdef DEBUG_REDUCE
578 printf("start_offset: %lu  end_offset: %lu\n",start_offset,end_offset);
579 printf("row_cnt before:  %lu\n",row_cnt);
580 #endif
581     if (start_offset) {
582         (*start) = (*start)-start_offset;
583         skiprows=reduce_factor-start_offset/cur_step;
584         srcptr+=skiprows* *ds_cnt;
585         for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
586         row_cnt-=skiprows;
587     }
588 #ifdef DEBUG_REDUCE
589 printf("row_cnt between: %lu\n",row_cnt);
590 #endif
592     /* At the end we have some rows that are not going to be
593     ** used, the amount is end_offset/cur_step
594     */
595     if (end_offset) {
596         (*end) = (*end)-end_offset+(*step);
597         skiprows = end_offset/cur_step;
598         row_cnt-=skiprows;
599     }
600 #ifdef DEBUG_REDUCE
601 printf("row_cnt after:   %lu\n",row_cnt);
602 #endif
604 /* Sanity check: row_cnt should be multiple of reduce_factor */
605 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
607     if (row_cnt%reduce_factor) {
608         printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
609                                 row_cnt,reduce_factor);
610         printf("BUG in reduce_data()\n");
611         exit(1);
612     }
614     /* Now combine reduce_factor intervals at a time
615     ** into one interval for the destination.
616     */
618     for (dst_row=0;(long int)row_cnt>=reduce_factor;dst_row++) {
619         for (col=0;col<(*ds_cnt);col++) {
620             rrd_value_t newval=DNAN;
621             unsigned long validval=0;
623             for (i=0;i<reduce_factor;i++) {
624                 if (isnan(srcptr[i*(*ds_cnt)+col])) {
625                     continue;
626                 }
627                 validval++;
628                 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
629                 else {
630                     switch (cf) {
631                         case CF_HWPREDICT:
632                         case CF_DEVSEASONAL:
633                         case CF_DEVPREDICT:
634                         case CF_SEASONAL:
635                         case CF_AVERAGE:
636                             newval += srcptr[i*(*ds_cnt)+col];
637                             break;
638                         case CF_MINIMUM:
639                             newval = min (newval,srcptr[i*(*ds_cnt)+col]);
640                             break;
641                         case CF_FAILURES: 
642                         /* an interval contains a failure if any subintervals contained a failure */
643                         case CF_MAXIMUM:
644                             newval = max (newval,srcptr[i*(*ds_cnt)+col]);
645                             break;
646                         case CF_LAST:
647                             newval = srcptr[i*(*ds_cnt)+col];
648                             break;
649                     }
650                 }
651             }
652             if (validval == 0){newval = DNAN;} else{
653                 switch (cf) {
654                     case CF_HWPREDICT:
655                 case CF_DEVSEASONAL:
656                     case CF_DEVPREDICT:
657                     case CF_SEASONAL:
658                     case CF_AVERAGE:                
659                        newval /= validval;
660                         break;
661                     case CF_MINIMUM:
662                     case CF_FAILURES:
663                      case CF_MAXIMUM:
664                     case CF_LAST:
665                         break;
666                 }
667             }
668             *dstptr++=newval;
669         }
670         srcptr+=(*ds_cnt)*reduce_factor;
671         row_cnt-=reduce_factor;
672     }
673     /* If we had to alter the endtime, we didn't have enough
674     ** source rows to fill the last row. Fill it with NaN.
675     */
676     if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
677 #ifdef DEBUG_REDUCE
678     row_cnt = ((*end)-(*start))/ *step;
679     srcptr = *data;
680     printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
681                                 row_cnt,*start,*end,*step);
682 for (col=0;col<row_cnt;col++) {
683     printf("time %10lu: ",*start+(col+1)*(*step));
684     for (i=0;i<*ds_cnt;i++)
685         printf(" %8.2e",srcptr[*ds_cnt*col+i]);
686     printf("\n");
688 #endif
692 /* get the data required for the graphs from the 
693    relevant rrds ... */
695 int
696 data_fetch(image_desc_t *im )
698     int i,ii;
699     int                skip;
701     /* pull the data from the rrd files ... */
702     for (i=0;i< (int)im->gdes_c;i++){
703         /* only GF_DEF elements fetch data */
704         if (im->gdes[i].gf != GF_DEF) 
705             continue;
707         skip=0;
708         /* do we have it already ?*/
709         for (ii=0;ii<i;ii++) {
710             if (im->gdes[ii].gf != GF_DEF) 
711                 continue;
712             if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
713                         && (im->gdes[i].cf    == im->gdes[ii].cf)
714                         && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
715                         && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
716                         && (im->gdes[i].end_orig   == im->gdes[ii].end_orig)
717                         && (im->gdes[i].step_orig  == im->gdes[ii].step_orig)) {
718                 /* OK, the data is already there.
719                 ** Just copy the header portion
720                 */
721                 im->gdes[i].start = im->gdes[ii].start;
722                 im->gdes[i].end = im->gdes[ii].end;
723                 im->gdes[i].step = im->gdes[ii].step;
724                 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
725                 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;                
726                 im->gdes[i].data = im->gdes[ii].data;
727                 im->gdes[i].data_first = 0;
728                 skip=1;
729             }
730             if (skip) 
731                 break;
732         }
733         if (! skip) {
734             unsigned long  ft_step = im->gdes[i].step ; /* ft_step will record what we got from fetch */
735             
736             if((rrd_fetch_fn(im->gdes[i].rrd,
737                              im->gdes[i].cf,
738                              &im->gdes[i].start,
739                              &im->gdes[i].end,
740                              &ft_step,
741                              &im->gdes[i].ds_cnt,
742                              &im->gdes[i].ds_namv,
743                              &im->gdes[i].data)) == -1){                
744                 return -1;
745             }
746             im->gdes[i].data_first = 1;            
747         
748             if (ft_step < im->gdes[i].step) {
749                 reduce_data(im->gdes[i].cf_reduce,
750                             ft_step,
751                             &im->gdes[i].start,
752                             &im->gdes[i].end,
753                             &im->gdes[i].step,
754                             &im->gdes[i].ds_cnt,
755                             &im->gdes[i].data);
756             } else {
757                 im->gdes[i].step = ft_step;
758             }
759         }
760         
761         /* lets see if the required data source is really there */
762         for(ii=0;ii<(int)im->gdes[i].ds_cnt;ii++){
763             if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
764                 im->gdes[i].ds=ii; }
765         }
766         if (im->gdes[i].ds== -1){
767             rrd_set_error("No DS called '%s' in '%s'",
768                           im->gdes[i].ds_nam,im->gdes[i].rrd);
769             return -1; 
770         }
771         
772     }
773     return 0;
776 /* evaluate the expressions in the CDEF functions */
778 /*************************************************************
779  * CDEF stuff 
780  *************************************************************/
782 long
783 find_var_wrapper(void *arg1, char *key)
785    return find_var((image_desc_t *) arg1, key);
788 /* find gdes containing var*/
789 long
790 find_var(image_desc_t *im, char *key){
791     long ii;
792     for(ii=0;ii<im->gdes_c-1;ii++){
793         if((im->gdes[ii].gf == GF_DEF 
794             || im->gdes[ii].gf == GF_VDEF
795             || im->gdes[ii].gf == GF_CDEF) 
796            && (strcmp(im->gdes[ii].vname,key) == 0)){
797             return ii; 
798         }           
799     }                        
800     return -1;
803 /* find the largest common denominator for all the numbers
804    in the 0 terminated num array */
805 long
806 lcd(long *num){
807     long rest;
808     int i;
809     for (i=0;num[i+1]!=0;i++){
810         do { 
811             rest=num[i] % num[i+1];
812             num[i]=num[i+1]; num[i+1]=rest;
813         } while (rest!=0);
814         num[i+1] = num[i];
815     }
816 /*    return i==0?num[i]:num[i-1]; */
817       return num[i];
820 /* run the rpn calculator on all the VDEF and CDEF arguments */
821 int
822 data_calc( image_desc_t *im){
824     int       gdi;
825     int       dataidx;
826     long      *steparray, rpi;
827     int       stepcnt;
828     time_t    now;
829     rpnstack_t rpnstack;
831     rpnstack_init(&rpnstack);
833     for (gdi=0;gdi<im->gdes_c;gdi++){
834         /* Look for GF_VDEF and GF_CDEF in the same loop,
835          * so CDEFs can use VDEFs and vice versa
836          */
837         switch (im->gdes[gdi].gf) {
838             case GF_XPORT:
839               break;
840             case GF_SHIFT: {
841                 graph_desc_t        *vdp = &im->gdes[im->gdes[gdi].vidx];
842                 
843                 /* remove current shift */
844                 vdp->start -= vdp->shift;
845                 vdp->end -= vdp->shift;
846                 
847                 /* vdef */
848                 if (im->gdes[gdi].shidx >= 0) 
849                         vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
850                 /* constant */
851                 else
852                         vdp->shift = im->gdes[gdi].shval;
854                 /* normalize shift to multiple of consolidated step */
855                 vdp->shift = (vdp->shift / (long)vdp->step) * (long)vdp->step;
857                 /* apply shift */
858                 vdp->start += vdp->shift;
859                 vdp->end += vdp->shift;
860                 break;
861             }
862             case GF_VDEF:
863                 /* A VDEF has no DS.  This also signals other parts
864                  * of rrdtool that this is a VDEF value, not a CDEF.
865                  */
866                 im->gdes[gdi].ds_cnt = 0;
867                 if (vdef_calc(im,gdi)) {
868                     rrd_set_error("Error processing VDEF '%s'"
869                         ,im->gdes[gdi].vname
870                         );
871                     rpnstack_free(&rpnstack);
872                     return -1;
873                 }
874                 break;
875             case GF_CDEF:
876                 im->gdes[gdi].ds_cnt = 1;
877                 im->gdes[gdi].ds = 0;
878                 im->gdes[gdi].data_first = 1;
879                 im->gdes[gdi].start = 0;
880                 im->gdes[gdi].end = 0;
881                 steparray=NULL;
882                 stepcnt = 0;
883                 dataidx=-1;
885                 /* Find the variables in the expression.
886                  * - VDEF variables are substituted by their values
887                  *   and the opcode is changed into OP_NUMBER.
888                  * - CDEF variables are analized for their step size,
889                  *   the lowest common denominator of all the step
890                  *   sizes of the data sources involved is calculated
891                  *   and the resulting number is the step size for the
892                  *   resulting data source.
893                  */
894                 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
895                     if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE  ||
896                         im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
897                         long ptr = im->gdes[gdi].rpnp[rpi].ptr;
898                         if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
899 #if 0
900                             printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
901                                im->gdes[gdi].vname,
902                                im->gdes[ptr].vname);
903                             printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
904 #endif
905                             im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
906                             im->gdes[gdi].rpnp[rpi].op  = OP_NUMBER;
907                         } else { /* normal variables and PREF(variables) */
909                             /* add one entry to the array that keeps track of the step sizes of the
910                              * data sources going into the CDEF. */
911                             if ((steparray =
912                                  rrd_realloc(steparray,
913                                                          (++stepcnt+1)*sizeof(*steparray)))==NULL){
914                                   rrd_set_error("realloc steparray");
915                                   rpnstack_free(&rpnstack);
916                                  return -1;
917                             };
919                             steparray[stepcnt-1] = im->gdes[ptr].step;
921                             /* adjust start and end of cdef (gdi) so
922                              * that it runs from the latest start point
923                              * to the earliest endpoint of any of the
924                              * rras involved (ptr)
925                              */
927                             if(im->gdes[gdi].start < im->gdes[ptr].start)
928                                 im->gdes[gdi].start = im->gdes[ptr].start;
930                             if(im->gdes[gdi].end == 0 ||
931                                         im->gdes[gdi].end > im->gdes[ptr].end)
932                                 im->gdes[gdi].end = im->gdes[ptr].end;
933                 
934                             /* store pointer to the first element of
935                              * the rra providing data for variable,
936                              * further save step size and data source
937                              * count of this rra
938                              */ 
939                             im->gdes[gdi].rpnp[rpi].data   = im->gdes[ptr].data + im->gdes[ptr].ds;
940                             im->gdes[gdi].rpnp[rpi].step   = im->gdes[ptr].step;
941                             im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
943                             /* backoff the *.data ptr; this is done so
944                              * rpncalc() function doesn't have to treat
945                              * the first case differently
946                              */
947                         } /* if ds_cnt != 0 */
948                     } /* if OP_VARIABLE */
949                 } /* loop through all rpi */
951                 /* move the data pointers to the correct period */
952                 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
953                     if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
954                         im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
955                         long ptr  = im->gdes[gdi].rpnp[rpi].ptr;
956                         long diff = im->gdes[gdi].start - im->gdes[ptr].start;
958                         if(diff > 0)
959                             im->gdes[gdi].rpnp[rpi].data += (diff / im->gdes[ptr].step) * im->gdes[ptr].ds_cnt;
960                      }
961                 }
963                 if(steparray == NULL){
964                     rrd_set_error("rpn expressions without DEF"
965                                 " or CDEF variables are not supported");
966                     rpnstack_free(&rpnstack);
967                     return -1;    
968                 }
969                 steparray[stepcnt]=0;
970                 /* Now find the resulting step.  All steps in all
971                  * used RRAs have to be visited
972                  */
973                 im->gdes[gdi].step = lcd(steparray);
974                 free(steparray);
975                 if((im->gdes[gdi].data = malloc((
976                                 (im->gdes[gdi].end-im->gdes[gdi].start) 
977                                     / im->gdes[gdi].step)
978                                     * sizeof(double)))==NULL){
979                     rrd_set_error("malloc im->gdes[gdi].data");
980                     rpnstack_free(&rpnstack);
981                     return -1;
982                 }
983         
984                 /* Step through the new cdef results array and
985                  * calculate the values
986                  */
987                 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
988                                 now<=im->gdes[gdi].end;
989                                 now += im->gdes[gdi].step)
990                 {
991                     rpnp_t  *rpnp = im -> gdes[gdi].rpnp;
993                     /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
994                      * in this case we are advancing by timesteps;
995                      * we use the fact that time_t is a synonym for long
996                      */
997                     if (rpn_calc(rpnp,&rpnstack,(long) now, 
998                                 im->gdes[gdi].data,++dataidx) == -1) {
999                         /* rpn_calc sets the error string */
1000                         rpnstack_free(&rpnstack); 
1001                         return -1;
1002                     } 
1003                 } /* enumerate over time steps within a CDEF */
1004                 break;
1005             default:
1006                 continue;
1007         }
1008     } /* enumerate over CDEFs */
1009     rpnstack_free(&rpnstack);
1010     return 0;
1013 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
1016     int aInt = *(int*)&A;
1017     int bInt = *(int*)&B;
1018     int intDiff;
1019     /* Make sure maxUlps is non-negative and small enough that the
1020        default NAN won't compare as equal to anything.  */
1022     /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1024     /* Make aInt lexicographically ordered as a twos-complement int */
1026     if (aInt < 0)
1027         aInt = 0x80000000l - aInt;
1029     /* Make bInt lexicographically ordered as a twos-complement int */
1031     if (bInt < 0)
1032         bInt = 0x80000000l - bInt;
1034     intDiff = abs(aInt - bInt);
1036     if (intDiff <= maxUlps)
1037         return 1;
1039     return 0;
1042 /* massage data so, that we get one value for each x coordinate in the graph */
1043 int
1044 data_proc( image_desc_t *im ){
1045     long i,ii;
1046     double pixstep = (double)(im->end-im->start)
1047         /(double)im->xsize; /* how much time 
1048                                passes in one pixel */
1049     double paintval;
1050     double minval=DNAN,maxval=DNAN;
1051     
1052     unsigned long gr_time;    
1054     /* memory for the processed data */
1055     for(i=0;i<im->gdes_c;i++) {
1056         if((im->gdes[i].gf==GF_LINE) ||
1057                 (im->gdes[i].gf==GF_AREA) ||
1058                 (im->gdes[i].gf==GF_TICK)) {
1059             if((im->gdes[i].p_data = malloc((im->xsize +1)
1060                                         * sizeof(rrd_value_t)))==NULL){
1061                 rrd_set_error("malloc data_proc");
1062                 return -1;
1063             }
1064         }
1065     }
1067     for (i=0;i<im->xsize;i++) {        /* for each pixel */
1068         long vidx;
1069         gr_time = im->start+pixstep*i; /* time of the current step */
1070         paintval=0.0;
1071         
1072         for (ii=0;ii<im->gdes_c;ii++) {
1073             double value;
1074             switch (im->gdes[ii].gf) {
1075                 case GF_LINE:
1076                 case GF_AREA:
1077                 case GF_TICK:
1078                     if (!im->gdes[ii].stack)
1079                         paintval = 0.0;
1080                     value = im->gdes[ii].yrule;
1081                     if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1082                         /* The time of the data doesn't necessarily match
1083                         ** the time of the graph. Beware.
1084                         */
1085                         vidx = im->gdes[ii].vidx;
1086                         if (im->gdes[vidx].gf == GF_VDEF) {
1087                             value = im->gdes[vidx].vf.val;
1088                         } else if (((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1089                                    ((long int)gr_time < (long int)im->gdes[vidx].end) ) {
1090                             value = im->gdes[vidx].data[
1091                                 (unsigned long) floor(
1092                                     (double)(gr_time - im->gdes[vidx].start)
1093                                                 / im->gdes[vidx].step)
1094                                 * im->gdes[vidx].ds_cnt
1095                                 + im->gdes[vidx].ds
1096                             ];
1097                         } else {
1098                             value = DNAN;
1099                         }
1100                     };
1102                     if (! isnan(value)) {
1103                         paintval += value;
1104                         im->gdes[ii].p_data[i] = paintval;
1105                         /* GF_TICK: the data values are not
1106                         ** relevant for min and max
1107                         */
1108                         if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1109                             if ((isnan(minval) || paintval <  minval ) &&
1110                               ! (im->logarithmic && paintval <= 0.0)) 
1111                                         minval = paintval;
1112                             if (isnan(maxval) || paintval >  maxval)
1113                                 maxval = paintval;
1114                         }
1115                     } else {
1116                         im->gdes[ii].p_data[i] = DNAN;
1117                     }
1118                     break;
1119                 case GF_STACK:
1120                     rrd_set_error("STACK should already be turned into LINE or AREA here");
1121                     return -1;
1122                     break;
1123                 default:
1124                     break;
1125             }
1126         }
1127     }
1129     /* if min or max have not been asigned a value this is because
1130        there was no data in the graph ... this is not good ...
1131        lets set these to dummy values then ... */
1133     if (im->logarithmic) {
1134         if (isnan(minval)) minval = 0.2;
1135         if (isnan(maxval)) maxval = 5.1;
1136     }
1137     else {
1138         if (isnan(minval)) minval = 0.0;
1139         if (isnan(maxval)) maxval = 1.0;
1140     }
1141     
1142     /* adjust min and max values */
1143     if (isnan(im->minval) 
1144         /* don't adjust low-end with log scale */ /* why not? */
1145         || ((!im->rigid) && im->minval > minval)
1146         ) {
1147         if (im->logarithmic)
1148             im->minval = minval * 0.5;
1149         else
1150             im->minval = minval;
1151     }
1152     if (isnan(im->maxval) 
1153         || (!im->rigid && im->maxval < maxval)
1154         ) {
1155         if (im->logarithmic)
1156             im->maxval = maxval * 2.0;
1157         else
1158             im->maxval = maxval;
1159     }
1160     /* make sure min is smaller than max */
1161     if (im->minval > im->maxval ) {             
1162         if (im->maxval > 0)
1163             im->minval = 0.99 * im->maxval;
1164         else 
1165             im->minval = 1.01 * im->maxval;
1166     }
1167                       
1168     /* make sure min and max are not equal */
1169    if (  AlmostEqual2sComplement(im->minval,im->maxval,4)) {
1170         if (im->maxval > 0)
1171            im->maxval *= 1.01; 
1172         else 
1173            im->maxval *= 0.99;
1175         if (! im->logarithmic) {
1176             if (im->minval > 0)
1177                im->minval *= 0.99;
1178             else 
1179                im->minval *= 1.01;
1180         }
1181         /* make sure min and max are not both zero */
1182         if (AlmostEqual2sComplement(im->maxval,0,4)) {
1183             im->maxval = 1.0;
1184         }
1185     }
1186     return 0;
1191 /* identify the point where the first gridline, label ... gets placed */
1193 time_t
1194 find_first_time(
1195     time_t   start, /* what is the initial time */
1196     enum tmt_en baseint,  /* what is the basic interval */
1197     long     basestep /* how many if these do we jump a time */
1198     )
1200     struct tm tm;
1201     localtime_r(&start, &tm);
1202     switch(baseint){
1203     case TMT_SECOND:
1204         tm.tm_sec -= tm.tm_sec % basestep; break;
1205     case TMT_MINUTE: 
1206         tm.tm_sec=0;
1207         tm.tm_min -= tm.tm_min % basestep; 
1208         break;
1209     case TMT_HOUR:
1210         tm.tm_sec=0;
1211         tm.tm_min = 0;
1212         tm.tm_hour -= tm.tm_hour % basestep; break;
1213     case TMT_DAY:
1214         /* we do NOT look at the basestep for this ... */
1215         tm.tm_sec=0;
1216         tm.tm_min = 0;
1217         tm.tm_hour = 0; break;
1218     case TMT_WEEK:
1219         /* we do NOT look at the basestep for this ... */
1220         tm.tm_sec=0;
1221         tm.tm_min = 0;
1222         tm.tm_hour = 0;
1223         tm.tm_mday -= tm.tm_wday -1;        /* -1 because we want the monday */
1224         if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1225         break;
1226     case TMT_MONTH:
1227         tm.tm_sec=0;
1228         tm.tm_min = 0;
1229         tm.tm_hour = 0;
1230         tm.tm_mday = 1;
1231         tm.tm_mon -= tm.tm_mon % basestep; break;
1233     case TMT_YEAR:
1234         tm.tm_sec=0;
1235         tm.tm_min = 0;
1236         tm.tm_hour = 0;
1237         tm.tm_mday = 1;
1238         tm.tm_mon = 0;
1239         tm.tm_year -= (tm.tm_year+1900) % basestep;
1240         
1241     }
1242     return mktime(&tm);
1244 /* identify the point where the next gridline, label ... gets placed */
1245 time_t 
1246 find_next_time(
1247     time_t   current, /* what is the initial time */
1248     enum tmt_en baseint,  /* what is the basic interval */
1249     long     basestep /* how many if these do we jump a time */
1250     )
1252     struct tm tm;
1253     time_t madetime;
1254     localtime_r(&current, &tm);
1255     do {
1256         switch(baseint){
1257         case TMT_SECOND:
1258             tm.tm_sec += basestep; break;
1259         case TMT_MINUTE: 
1260             tm.tm_min += basestep; break;
1261         case TMT_HOUR:
1262             tm.tm_hour += basestep; break;
1263         case TMT_DAY:
1264             tm.tm_mday += basestep; break;
1265         case TMT_WEEK:
1266             tm.tm_mday += 7*basestep; break;
1267         case TMT_MONTH:
1268             tm.tm_mon += basestep; break;
1269         case TMT_YEAR:
1270             tm.tm_year += basestep;        
1271         }
1272         madetime = mktime(&tm);
1273     } while (madetime == -1); /* this is necessary to skip impssible times
1274                                  like the daylight saving time skips */
1275     return madetime;
1276           
1280 /* calculate values required for PRINT and GPRINT functions */
1282 int
1283 print_calc(image_desc_t *im, char ***prdata) 
1285     long i,ii,validsteps;
1286     double printval;
1287     struct tm tmvdef;
1288     int graphelement = 0;
1289     long vidx;
1290     int max_ii;        
1291     double magfact = -1;
1292     char *si_symb = "";
1293     char *percent_s;
1294     int prlines = 1;
1295     /* wow initializing tmvdef is quite a task :-) */
1296     time_t now = time(NULL);
1297     localtime_r(&now,&tmvdef);
1298     if (im->imginfo) prlines++;
1299     for(i=0;i<im->gdes_c;i++){
1300             vidx = im->gdes[i].vidx;
1301         switch(im->gdes[i].gf){
1302         case GF_PRINT:
1303             prlines++;
1304             if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1305                 rrd_set_error("realloc prdata");
1306                 return 0;
1307             }
1308         case GF_GPRINT:
1309             /* PRINT and GPRINT can now print VDEF generated values.
1310              * There's no need to do any calculations on them as these
1311              * calculations were already made.
1312              */
1313             if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1314                 printval = im->gdes[vidx].vf.val;
1315                 localtime_r(&im->gdes[vidx].vf.when,&tmvdef);
1316             } else { /* need to calculate max,min,avg etcetera */
1317                 max_ii =((im->gdes[vidx].end 
1318                         - im->gdes[vidx].start)
1319                         / im->gdes[vidx].step
1320                         * im->gdes[vidx].ds_cnt);
1321                 printval = DNAN;
1322                 validsteps = 0;
1323                 for(        ii=im->gdes[vidx].ds;
1324                         ii < max_ii;
1325                         ii+=im->gdes[vidx].ds_cnt){
1326                     if (! finite(im->gdes[vidx].data[ii]))
1327                         continue;
1328                     if (isnan(printval)){
1329                         printval = im->gdes[vidx].data[ii];
1330                         validsteps++;
1331                         continue;
1332                     }
1334                     switch (im->gdes[i].cf){
1335                         case CF_HWPREDICT:
1336                         case CF_DEVPREDICT:
1337                         case CF_DEVSEASONAL:
1338                         case CF_SEASONAL:
1339                         case CF_AVERAGE:
1340                             validsteps++;
1341                             printval += im->gdes[vidx].data[ii];
1342                             break;
1343                         case CF_MINIMUM:
1344                             printval = min( printval, im->gdes[vidx].data[ii]);
1345                             break;
1346                         case CF_FAILURES:
1347                         case CF_MAXIMUM:
1348                             printval = max( printval, im->gdes[vidx].data[ii]);
1349                             break;
1350                         case CF_LAST:
1351                             printval = im->gdes[vidx].data[ii];
1352                     }
1353                 }
1354                 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1355                     if (validsteps > 1) {
1356                         printval = (printval / validsteps);
1357                     }
1358                 }
1359             } /* prepare printval */
1361             if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1362                 /* Magfact is set to -1 upon entry to print_calc.  If it
1363                  * is still less than 0, then we need to run auto_scale.
1364                  * Otherwise, put the value into the correct units.  If
1365                  * the value is 0, then do not set the symbol or magnification
1366                  * so next the calculation will be performed again. */
1367                 if (magfact < 0.0) {
1368                     auto_scale(im,&printval,&si_symb,&magfact);
1369                     if (printval == 0.0)
1370                         magfact = -1.0;
1371                 } else {
1372                     printval /= magfact;
1373                 }
1374                 *(++percent_s) = 's';
1375             } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1376                 auto_scale(im,&printval,&si_symb,&magfact);
1377             }
1379             if (im->gdes[i].gf == GF_PRINT){
1380                 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1381                 (*prdata)[prlines-1] = NULL;
1382                 if (im->gdes[i].strftm){
1383                         strftime((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1384                 } else {
1385                      if (bad_format(im->gdes[i].format)) {
1386                           rrd_set_error("bad format for PRINT in '%s'", im->gdes[i].format);
1387                         return -1;
1388                   }
1390 #ifdef HAVE_SNPRINTF
1391                   snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1392 #else
1393                   sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1394 #endif
1395                }
1396              } else {
1397                 /* GF_GPRINT */
1399                 if (im->gdes[i].strftm){
1400                         strftime(im->gdes[i].legend,FMT_LEG_LEN,im->gdes[i].format,&tmvdef);
1401                 } else {
1402                     if (bad_format(im->gdes[i].format)) {
1403                         rrd_set_error("bad format for GPRINT in '%s'", im->gdes[i].format);
1404                         return -1;
1405                   }
1406 #ifdef HAVE_SNPRINTF
1407                   snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1408 #else
1409                   sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1410 #endif
1411                 }
1412                 graphelement = 1;               
1413             }            
1414             break;
1415         case GF_LINE:
1416         case GF_AREA:
1417         case GF_TICK:
1418             graphelement = 1;
1419             break;
1420         case GF_HRULE:
1421             if(isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1422                im->gdes[i].yrule=im->gdes[vidx].vf.val;
1423             };
1424             graphelement = 1;
1425             break;
1426         case GF_VRULE:
1427             if(im->gdes[i].xrule == 0) { /* again ... the legend printer needs it*/
1428               im->gdes[i].xrule = im->gdes[vidx].vf.when;
1429             };
1430             graphelement = 1;
1431             break;
1432         case GF_COMMENT:
1433         case GF_DEF:
1434         case GF_CDEF:            
1435         case GF_VDEF:            
1436 #ifdef WITH_PIECHART
1437         case GF_PART:
1438 #endif
1439         case GF_SHIFT:
1440         case GF_XPORT:
1441             break;
1442         case GF_STACK:
1443             rrd_set_error("STACK should already be turned into LINE or AREA here");
1444             return -1;
1445             break;
1446         }
1447     }
1448     return graphelement;
1452 /* place legends with color spots */
1453 int
1454 leg_place(image_desc_t *im)
1456     /* graph labels */
1457     int   interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1458     int   border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1459     int   fill=0, fill_last;
1460     int   leg_c = 0;
1461     int   leg_x = border, leg_y = im->yimg;
1462     int   leg_y_prev = im->yimg;
1463     int   leg_cc;
1464     int   glue = 0;
1465     int   i,ii, mark = 0;
1466     char  prt_fctn; /*special printfunctions */
1467     int  *legspace;
1469   if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1470     if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1471        rrd_set_error("malloc for legspace");
1472        return -1;
1473     }
1475     for(i=0;i<im->gdes_c;i++){
1476         fill_last = fill;
1477         
1478         /* hid legends for rules which are not displayed */
1479         
1480         if(!(im->extra_flags & FORCE_RULES_LEGEND)) {
1481                 if (im->gdes[i].gf == GF_HRULE &&
1482                     (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1483                     im->gdes[i].legend[0] = '\0';
1485                 if (im->gdes[i].gf == GF_VRULE &&
1486                     (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1487                     im->gdes[i].legend[0] = '\0';
1488         }
1490         leg_cc = strlen(im->gdes[i].legend);
1491         
1492         /* is there a controle code ant the end of the legend string ? */ 
1493         /* and it is not a tab \\t */
1494         if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\' && im->gdes[i].legend[leg_cc-1] != 't') {
1495             prt_fctn = im->gdes[i].legend[leg_cc-1];
1496             leg_cc -= 2;
1497             im->gdes[i].legend[leg_cc] = '\0';
1498         } else {
1499             prt_fctn = '\0';
1500         }
1501         /* only valid control codes */
1502         if (prt_fctn != 'l' && 
1503             prt_fctn != 'n' && /* a synonym for l */
1504             prt_fctn != 'r' &&
1505             prt_fctn != 'j' &&
1506             prt_fctn != 'c' &&
1507             prt_fctn != 's' &&
1508             prt_fctn != 't' &&
1509             prt_fctn != '\0' &&
1510             prt_fctn != 'g' ) {
1511                free(legspace);
1512                rrd_set_error("Unknown control code at the end of '%s\\%c'",im->gdes[i].legend,prt_fctn);
1513                       return -1;
1515         }
1517         /* remove exess space */
1518         if ( prt_fctn == 'n' ){
1519             prt_fctn='l';
1520         }
1522         while (prt_fctn=='g' && 
1523                leg_cc > 0 && 
1524                im->gdes[i].legend[leg_cc-1]==' '){
1525            leg_cc--;
1526            im->gdes[i].legend[leg_cc]='\0';
1527         }
1528         if (leg_cc != 0 ){
1529            legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1530            
1531            if (fill > 0){ 
1532                 /* no interleg space if string ends in \g */
1533                fill += legspace[i];
1534             }
1535            fill += gfx_get_text_width(im->canvas, fill+border,
1536                                       im->text_prop[TEXT_PROP_LEGEND].font,
1537                                       im->text_prop[TEXT_PROP_LEGEND].size,
1538                                       im->tabwidth,
1539                                       im->gdes[i].legend, 0);
1540             leg_c++;
1541         } else {
1542            legspace[i]=0;
1543         }
1544         /* who said there was a special tag ... ?*/
1545         if (prt_fctn=='g') {    
1546            prt_fctn = '\0';
1547         }
1548         if (prt_fctn == '\0') {
1549             if (i == im->gdes_c -1 ) prt_fctn ='l';
1550             
1551             /* is it time to place the legends ? */
1552             if (fill > im->ximg - 2*border){
1553                 if (leg_c > 1) {
1554                     /* go back one */
1555                     i--; 
1556                     fill = fill_last;
1557                     leg_c--;
1558                     prt_fctn = 'j';
1559                 } else {
1560                     prt_fctn = 'l';
1561                 }
1562                 
1563             }
1564         }
1567         if (prt_fctn != '\0'){        
1568             leg_x = border;
1569             if (leg_c >= 2 && prt_fctn == 'j') {
1570                 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1571             } else {
1572                 glue = 0;
1573             }
1574             if (prt_fctn =='c') leg_x =  (im->ximg - fill) / 2.0;
1575             if (prt_fctn =='r') leg_x =  im->ximg - fill - border;
1577             for(ii=mark;ii<=i;ii++){
1578                 if(im->gdes[ii].legend[0]=='\0')
1579                     continue; /* skip empty legends */
1580                 im->gdes[ii].leg_x = leg_x;
1581                 im->gdes[ii].leg_y = leg_y;
1582                 leg_x += 
1583                  gfx_get_text_width(im->canvas, leg_x,
1584                                       im->text_prop[TEXT_PROP_LEGEND].font,
1585                                       im->text_prop[TEXT_PROP_LEGEND].size,
1586                                       im->tabwidth,
1587                                       im->gdes[ii].legend, 0) 
1588                    + legspace[ii]
1589                    + glue;
1590             }                        
1591             leg_y_prev = leg_y;
1592             /* only add y space if there was text on the line */
1593             if (leg_x > border || prt_fctn == 's')            
1594                leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1595             if (prt_fctn == 's')
1596                leg_y -=  im->text_prop[TEXT_PROP_LEGEND].size;           
1597             fill = 0;
1598             leg_c = 0;
1599             mark = ii;
1600         }           
1601     }
1602     im->yimg = leg_y_prev;
1603     /* if we did place some legends we have to add vertical space */
1604     if (leg_y != im->yimg){
1605         im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1606     }
1607     free(legspace);
1608   }
1609   return 0;
1612 /* create a grid on the graph. it determines what to do
1613    from the values of xsize, start and end */
1615 /* the xaxis labels are determined from the number of seconds per pixel
1616    in the requested graph */
1620 int
1621 calc_horizontal_grid(image_desc_t   *im)
1623     double   range;
1624     double   scaledrange;
1625     int      pixel,i;
1626     int      gridind=0;
1627     int      decimals, fractionals;
1629     im->ygrid_scale.labfact=2;
1630     range =  im->maxval - im->minval;
1631     scaledrange = range / im->magfact;
1633         /* does the scale of this graph make it impossible to put lines
1634            on it? If so, give up. */
1635         if (isnan(scaledrange)) {
1636                 return 0;
1637         }
1639     /* find grid spaceing */
1640     pixel=1;
1641     if(isnan(im->ygridstep)){
1642         if(im->extra_flags & ALTYGRID) {
1643             /* find the value with max number of digits. Get number of digits */
1644             decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1645             if(decimals <= 0) /* everything is small. make place for zero */
1646                 decimals = 1;
1647             
1648             im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1649             
1650             if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1651                 im->ygrid_scale.gridstep = 0.1;
1652             /* should have at least 5 lines but no more then 15 */
1653             if(range/im->ygrid_scale.gridstep < 5)
1654                 im->ygrid_scale.gridstep /= 10;
1655             if(range/im->ygrid_scale.gridstep > 15)
1656                 im->ygrid_scale.gridstep *= 10;
1657             if(range/im->ygrid_scale.gridstep > 5) {
1658                 im->ygrid_scale.labfact = 1;
1659                 if(range/im->ygrid_scale.gridstep > 8)
1660                     im->ygrid_scale.labfact = 2;
1661             }
1662             else {
1663                 im->ygrid_scale.gridstep /= 5;
1664                 im->ygrid_scale.labfact = 5;
1665             }
1666             fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1667             if(fractionals < 0) { /* small amplitude. */
1668                 int len = decimals - fractionals + 1;
1669                 if (im->unitslength < len+2) im->unitslength = len+2;
1670                 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1671             } else {
1672                 int len = decimals + 1;
1673                 if (im->unitslength < len+2) im->unitslength = len+2;
1674                 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1675             }
1676         }
1677         else {
1678             for(i=0;ylab[i].grid > 0;i++){
1679                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1680                    gridind = i;
1681                 if (pixel > 7)
1682                     break;
1683             }
1684             
1685             for(i=0; i<4;i++) {
1686                if (pixel * ylab[gridind].lfac[i] >=  2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1687                   im->ygrid_scale.labfact =  ylab[gridind].lfac[i];
1688                   break;
1689                }
1690             } 
1691             
1692             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1693         }
1694     } else {
1695         im->ygrid_scale.gridstep = im->ygridstep;
1696         im->ygrid_scale.labfact = im->ylabfact;
1697     }
1698     return 1;
1701 int draw_horizontal_grid(image_desc_t *im)
1703     int      i;
1704     double   scaledstep;
1705     char     graph_label[100];
1706     int      nlabels=0;
1707     double X0=im->xorigin;
1708     double X1=im->xorigin+im->xsize;
1709    
1710     int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1711     int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1712     double MaxY;
1713     double second_axis_magfact = 0;
1714     char *second_axis_symb = "";
1715     scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1716     MaxY = scaledstep*(double)egrid;
1717     for (i = sgrid; i <= egrid; i++){
1718        double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1719        double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
1720        if ( floor(Y0+0.5) >= im->yorigin-im->ysize 
1721             && floor(Y0+0.5) <= im->yorigin){       
1722             /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1723                with the chosen settings. Add a label if required by settings, or if
1724                there is only one label so far and the next grid line is out of bounds. */
1725             if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){                
1726                 if (im->symbol == ' ') {
1727                      if(im->extra_flags & ALTYGRID) {
1728                         sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1729                     } else {
1730                         if(MaxY < 10) {
1731                            sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1732                           } else {
1733                            sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1734                         }
1735                     }
1736                 }else {
1737                     char sisym = ( i == 0  ? ' ' : im->symbol);
1738                      if(im->extra_flags & ALTYGRID) {
1739                         sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1740                     } else {
1741                           if(MaxY < 10){
1742                              sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1743                         } else {
1744                              sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1745                         }
1746                     }
1747                 }
1748                 nlabels++;
1749                 if (im->second_axis_scale != 0){
1750                         char graph_label_right[100];
1751                         double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
1752                         if (im->second_axis_format[0] == '\0'){
1753                             if (!second_axis_magfact){
1754                                 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
1755                                 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
1756                             }
1757                             sval /= second_axis_magfact;
1758  
1759                             if(MaxY < 10) { 
1760                                 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
1761                             } else {
1762                                 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
1763                             }
1764                         }
1765                         else {
1766                            sprintf(graph_label_right,im->second_axis_format,sval);
1767                         }        
1768                         gfx_new_text ( im->canvas,
1769                                X1+7, Y0,
1770                                im->graph_col[GRC_FONT],
1771                                im->text_prop[TEXT_PROP_AXIS].font,
1772                                im->text_prop[TEXT_PROP_AXIS].size,
1773                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
1774                                graph_label_right );
1775                 }
1777                gfx_new_text ( im->canvas,
1778                               X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1779                               im->graph_col[GRC_FONT],
1780                               im->text_prop[TEXT_PROP_AXIS].font,
1781                               im->text_prop[TEXT_PROP_AXIS].size,
1782                               im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1783                               graph_label );
1784                gfx_new_dashed_line ( im->canvas,
1785                               X0-2,Y0,
1786                               X1+2,Y0,
1787                               MGRIDWIDTH, im->graph_col[GRC_MGRID],
1788                               im->grid_dash_on, im->grid_dash_off);               
1789                
1790             } else if (!(im->extra_flags & NOMINOR)) {                
1791                gfx_new_dashed_line ( im->canvas,
1792                               X0-1,Y0,
1793                               X1+1,Y0,
1794                               GRIDWIDTH, im->graph_col[GRC_GRID],
1795                               im->grid_dash_on, im->grid_dash_off);               
1796                
1797             }            
1798         }        
1799     } 
1800     return 1;
1803 /* this is frexp for base 10 */
1804 double frexp10(double, double *);
1805 double frexp10(double x, double *e) {
1806     double mnt;
1807     int iexp;
1809     iexp = floor(log(fabs(x)) / log(10));
1810     mnt = x / pow(10.0, iexp);
1811     if(mnt >= 10.0) {
1812         iexp++;
1813         mnt = x / pow(10.0, iexp);
1814     }
1815     *e = iexp;
1816     return mnt;
1819 /* logaritmic horizontal grid */
1820 int
1821 horizontal_log_grid(image_desc_t   *im)   
1823     double yloglab[][10] = {
1824         {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1825         {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1826         {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1827         {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1828         {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1829         {0,0,0,0,0, 0,0,0,0,0} /* last line */ };
1831     int i, j, val_exp, min_exp;
1832     double nex;                /* number of decades in data */
1833     double logscale;        /* scale in logarithmic space */
1834     int exfrac = 1;        /* decade spacing */
1835     int mid = -1;        /* row in yloglab for major grid */
1836     double mspac;        /* smallest major grid spacing (pixels) */
1837     int flab;                /* first value in yloglab to use */
1838     double value, tmp, pre_value;
1839     double X0,X1,Y0;   
1840     char graph_label[100];
1842     nex = log10(im->maxval / im->minval);
1843     logscale = im->ysize / nex;
1845     /* major spacing for data with high dynamic range */
1846     while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1847         if(exfrac == 1) exfrac = 3;
1848         else exfrac += 3;
1849     }
1851     /* major spacing for less dynamic data */
1852     do {
1853         /* search best row in yloglab */
1854         mid++;
1855         for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
1856         mspac = logscale * log10(10.0 / yloglab[mid][i]);
1857     } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
1858     if(mid) mid--;
1860     /* find first value in yloglab */
1861     for(flab = 0; yloglab[mid][flab] < 10 && frexp10(im->minval, &tmp) > yloglab[mid][flab] ; flab++);
1862     if(yloglab[mid][flab] == 10.0) {
1863         tmp += 1.0;
1864         flab = 0;
1865     }
1866     val_exp = tmp;
1867     if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
1869     X0=im->xorigin;
1870     X1=im->xorigin+im->xsize;
1872     /* draw grid */
1873     pre_value = DNAN;
1874     while(1) {       
1876         value = yloglab[mid][flab] * pow(10.0, val_exp);
1877         if (  AlmostEqual2sComplement(value,pre_value,4) ) break; /* it seems we are not converging */
1879         pre_value = value;
1881         Y0 = ytr(im, value);
1882         if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1884         /* major grid line */
1885         gfx_new_dashed_line ( im->canvas,
1886             X0-2,Y0,
1887             X1+2,Y0,
1888             MGRIDWIDTH, im->graph_col[GRC_MGRID],
1889             im->grid_dash_on, im->grid_dash_off);
1891         /* label */
1892         if (im->extra_flags & FORCE_UNITS_SI) {
1893             int scale;
1894             double pvalue;
1895             char symbol;
1897             scale = floor(val_exp / 3.0);
1898             if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
1899             else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
1900             pvalue *= yloglab[mid][flab];
1902             if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
1903                 ((scale+si_symbcenter) >= 0) )
1904                 symbol = si_symbol[scale+si_symbcenter];
1905             else
1906                 symbol = '?';
1908             sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1909         } else {
1910             sprintf(graph_label,"%3.0e", value);
1911         }
1912         if (im->second_axis_scale != 0){
1913                 char graph_label_right[100];
1914                 double sval = value*im->second_axis_scale+im->second_axis_shift;
1915                 if (im->second_axis_format[0] == '\0'){
1916                         if (im->extra_flags & FORCE_UNITS_SI) {
1917                                 double mfac = 1;
1918                                 char   *symb = "";
1919                                 auto_scale(im,&sval,&symb,&mfac);
1920                                 sprintf(graph_label_right,"%4.0f %s", sval,symb);
1921                         }
1922                         else {        
1923                                 sprintf(graph_label_right,"%3.0e", sval);
1924                         }
1925                 }
1926                 else {
1927                       sprintf(graph_label_right,im->second_axis_format,sval);
1928                 }    
1929     
1930                 gfx_new_text ( im->canvas,
1931                                X1+7, Y0,
1932                                im->graph_col[GRC_FONT],
1933                                im->text_prop[TEXT_PROP_AXIS].font,
1934                                im->text_prop[TEXT_PROP_AXIS].size,
1935                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
1936                                graph_label_right );
1937         }
1938             
1939         gfx_new_text ( im->canvas,
1940                        X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1941                        im->graph_col[GRC_FONT],
1942                        im->text_prop[TEXT_PROP_AXIS].font,
1943                        im->text_prop[TEXT_PROP_AXIS].size,
1944                        im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1945                        graph_label );
1947         /* minor grid */
1948         if(mid < 4 && exfrac == 1) {
1949             /* find first and last minor line behind current major line
1950              * i is the first line and j tha last */
1951             if(flab == 0) {
1952                 min_exp = val_exp - 1;
1953                 for(i = 1; yloglab[mid][i] < 10.0; i++);
1954                 i = yloglab[mid][i - 1] + 1;
1955                 j = 10;
1956             }
1957             else {
1958                 min_exp = val_exp;
1959                 i = yloglab[mid][flab - 1] + 1;
1960                 j = yloglab[mid][flab];
1961             }
1963             /* draw minor lines below current major line */
1964             for(; i < j; i++) {
1966                 value = i * pow(10.0, min_exp);
1967                 if(value < im->minval) continue;
1969                 Y0 = ytr(im, value);
1970                 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1972                 /* draw lines */
1973                 gfx_new_dashed_line ( im->canvas,
1974                     X0-1,Y0,
1975                     X1+1,Y0,
1976                     GRIDWIDTH, im->graph_col[GRC_GRID],
1977                     im->grid_dash_on, im->grid_dash_off);
1978             }
1979         }
1980         else if(exfrac > 1) {
1981             for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1982                 value = pow(10.0, i);
1983                 if(value < im->minval) continue;
1985                 Y0 = ytr(im, value);
1986                 if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
1988                 /* draw lines */
1989                 gfx_new_dashed_line ( im->canvas,
1990                     X0-1,Y0,
1991                     X1+1,Y0,
1992                     GRIDWIDTH, im->graph_col[GRC_GRID],
1993                     im->grid_dash_on, im->grid_dash_off);
1994             }
1995         }
1997         /* next decade */
1998         if(yloglab[mid][++flab] == 10.0) {
1999             flab = 0;
2000             val_exp += exfrac;
2001         }
2002     }
2004     /* draw minor lines after highest major line */
2005     if(mid < 4 && exfrac == 1) {
2006         /* find first and last minor line below current major line
2007          * i is the first line and j tha last */
2008         if(flab == 0) {
2009             min_exp = val_exp - 1;
2010             for(i = 1; yloglab[mid][i] < 10.0; i++);
2011             i = yloglab[mid][i - 1] + 1;
2012             j = 10;
2013         }
2014         else {
2015             min_exp = val_exp;
2016             i = yloglab[mid][flab - 1] + 1;
2017             j = yloglab[mid][flab];
2018         }
2020         /* draw minor lines below current major line */
2021         for(; i < j; i++) {
2023             value = i * pow(10.0, min_exp);
2024             if(value < im->minval) continue;
2026             Y0 = ytr(im, value);
2027             if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
2029             /* draw lines */
2030             gfx_new_dashed_line ( im->canvas,
2031                 X0-1,Y0,
2032                 X1+1,Y0,
2033                 GRIDWIDTH, im->graph_col[GRC_GRID],
2034                 im->grid_dash_on, im->grid_dash_off);
2035         }
2036     }
2037     /* fancy minor gridlines */
2038     else if(exfrac > 1) {
2039         for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2040             value = pow(10.0, i);
2041             if(value < im->minval) continue;
2043             Y0 = ytr(im, value);
2044             if(floor(Y0+0.5) <= im->yorigin - im->ysize) break;
2046             /* draw lines */
2047             gfx_new_dashed_line ( im->canvas,
2048                 X0-1,Y0,
2049                 X1+1,Y0,
2050                 GRIDWIDTH, im->graph_col[GRC_GRID],
2051                 im->grid_dash_on, im->grid_dash_off);
2052         }
2053     }
2055     return 1;
2059 void
2060 vertical_grid(
2061     image_desc_t   *im )
2062 {   
2063     int xlab_sel;                /* which sort of label and grid ? */
2064     time_t ti, tilab, timajor;
2065     long factor;
2066     char graph_label[100];
2067     double X0,Y0,Y1; /* points for filled graph and more*/
2068     struct tm tm;
2070     /* the type of time grid is determined by finding
2071        the number of seconds per pixel in the graph */
2072     
2073     
2074     if(im->xlab_user.minsec == -1){
2075         factor=(im->end - im->start)/im->xsize;
2076         xlab_sel=0;
2077         while ( xlab[xlab_sel+1].minsec != -1 
2078                 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; }        /* pick the last one */
2079         while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
2080                 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; }        /* go back to the smallest size */
2081         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2082         im->xlab_user.gridst = xlab[xlab_sel].gridst;
2083         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2084         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2085         im->xlab_user.labtm = xlab[xlab_sel].labtm;
2086         im->xlab_user.labst = xlab[xlab_sel].labst;
2087         im->xlab_user.precis = xlab[xlab_sel].precis;
2088         im->xlab_user.stst = xlab[xlab_sel].stst;
2089     }
2090     
2091     /* y coords are the same for every line ... */
2092     Y0 = im->yorigin;
2093     Y1 = im->yorigin-im->ysize;
2094    
2096     /* paint the minor grid */
2097     if (!(im->extra_flags & NOMINOR))
2098     {
2099         for(ti = find_first_time(im->start,
2100                                 im->xlab_user.gridtm,
2101                                 im->xlab_user.gridst),
2102             timajor = find_first_time(im->start,
2103                                 im->xlab_user.mgridtm,
2104                                 im->xlab_user.mgridst);
2105             ti < im->end; 
2106             ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
2107             ){
2108             /* are we inside the graph ? */
2109             if (ti < im->start || ti > im->end) continue;
2110             while (timajor < ti) {
2111                 timajor = find_next_time(timajor,
2112                         im->xlab_user.mgridtm, im->xlab_user.mgridst);
2113             }
2114             if (ti == timajor) continue; /* skip as falls on major grid line */
2115            X0 = xtr(im,ti);       
2116            gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
2117                im->graph_col[GRC_GRID],
2118                im->grid_dash_on, im->grid_dash_off);
2119            
2120         }
2121     }
2123     /* paint the major grid */
2124     for(ti = find_first_time(im->start,
2125                             im->xlab_user.mgridtm,
2126                             im->xlab_user.mgridst);
2127         ti < im->end; 
2128         ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
2129         ){
2130         /* are we inside the graph ? */
2131         if (ti < im->start || ti > im->end) continue;
2132        X0 = xtr(im,ti);
2133        gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
2134            im->graph_col[GRC_MGRID],
2135            im->grid_dash_on, im->grid_dash_off);
2136        
2137     }
2138     /* paint the labels below the graph */
2139     for(ti = find_first_time(im->start - im->xlab_user.precis/2,
2140                             im->xlab_user.labtm,
2141                             im->xlab_user.labst);
2142         ti <= im->end - im->xlab_user.precis/2; 
2143         ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
2144         ){
2145         tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
2146         /* are we inside the graph ? */
2147         if (tilab < im->start || tilab > im->end) continue;
2149 #if HAVE_STRFTIME
2150         localtime_r(&tilab, &tm);
2151         strftime(graph_label,99,im->xlab_user.stst, &tm);
2152 #else
2153 # error "your libc has no strftime I guess we'll abort the exercise here."
2154 #endif
2155        gfx_new_text ( im->canvas,
2156                       xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
2157                       im->graph_col[GRC_FONT],
2158                       im->text_prop[TEXT_PROP_AXIS].font,
2159                       im->text_prop[TEXT_PROP_AXIS].size,
2160                       im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
2161                       graph_label );
2162        
2163     }
2168 void 
2169 axis_paint(
2170    image_desc_t   *im
2171            )
2172 {   
2173     /* draw x and y axis */
2174     /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2175                       im->xorigin+im->xsize,im->yorigin-im->ysize,
2176                       GRIDWIDTH, im->graph_col[GRC_AXIS]);
2177        
2178        gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2179                          im->xorigin+im->xsize,im->yorigin-im->ysize,
2180                          GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2181    
2182        gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
2183                          im->xorigin+im->xsize+4,im->yorigin,
2184                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2185    
2186        gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
2187                          im->xorigin,im->yorigin-im->ysize-4,
2188                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2189    
2190     
2191     /* arrow for X and Y axis direction */
2192     gfx_new_area ( im->canvas, 
2193                    im->xorigin+im->xsize+2,  im->yorigin-2,
2194                    im->xorigin+im->xsize+2,  im->yorigin+3,
2195                    im->xorigin+im->xsize+7,  im->yorigin+0.5, /* LINEOFFSET */
2196                    im->graph_col[GRC_ARROW]);
2198     gfx_new_area ( im->canvas, 
2199                    im->xorigin-2,  im->yorigin-im->ysize-2,
2200                    im->xorigin+3,  im->yorigin-im->ysize-2,
2201                    im->xorigin+0.5,    im->yorigin-im->ysize-7, /* LINEOFFSET */
2202                    im->graph_col[GRC_ARROW]);
2204     if (im->second_axis_scale != 0){
2205        gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin+4,
2206                          im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2207                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2208        gfx_new_area ( im->canvas, 
2209                    im->xorigin+im->xsize-2,  im->yorigin-im->ysize-2,
2210                    im->xorigin+im->xsize+3,  im->yorigin-im->ysize-2,
2211                    im->xorigin+im->xsize+0.5,    im->yorigin-im->ysize-7, /* LINEOFFSET */
2212                    im->graph_col[GRC_ARROW]);
2213     }
2216 void
2217 grid_paint(image_desc_t   *im)
2218 {   
2219     long i;
2220     int res=0;
2221     double X0,Y0; /* points for filled graph and more*/
2222     gfx_node_t *node;
2224     /* draw 3d border */
2225     node = gfx_new_area (im->canvas, 0,im->yimg,
2226                                  2,im->yimg-2,
2227                                  2,2,im->graph_col[GRC_SHADEA]);
2228     gfx_add_point( node , im->ximg - 2, 2 );
2229     gfx_add_point( node , im->ximg, 0 );
2230     gfx_add_point( node , 0,0 );
2231 /*    gfx_add_point( node , 0,im->yimg ); */
2232    
2233     node =  gfx_new_area (im->canvas, 2,im->yimg-2,
2234                                   im->ximg-2,im->yimg-2,
2235                                   im->ximg - 2, 2,
2236                                  im->graph_col[GRC_SHADEB]);
2237     gfx_add_point( node ,   im->ximg,0);
2238     gfx_add_point( node ,   im->ximg,im->yimg);
2239     gfx_add_point( node ,   0,im->yimg);
2240 /*    gfx_add_point( node , 0,im->yimg ); */
2241    
2242    
2243     if (im->draw_x_grid == 1 )
2244       vertical_grid(im);
2245     
2246     if (im->draw_y_grid == 1){
2247         if(im->logarithmic){
2248                 res = horizontal_log_grid(im);
2249         } else {
2250                 res = draw_horizontal_grid(im);
2251         }
2252         
2253         /* dont draw horizontal grid if there is no min and max val */
2254         if (! res ) {
2255           char *nodata = "No Data found";
2256            gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
2257                         im->graph_col[GRC_FONT],
2258                         im->text_prop[TEXT_PROP_AXIS].font,
2259                         im->text_prop[TEXT_PROP_AXIS].size,
2260                         im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
2261                         nodata );           
2262         }
2263     }
2265     /* yaxis unit description */
2266     if (im->ylegend[0] != '\0'){
2267             gfx_new_text( im->canvas,
2268                   10, (im->yorigin - im->ysize/2),
2269                   im->graph_col[GRC_FONT],
2270                   im->text_prop[TEXT_PROP_UNIT].font,
2271                   im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth, 
2272                   RRDGRAPH_YLEGEND_ANGLE,
2273                   GFX_H_LEFT, GFX_V_CENTER,
2274                   im->ylegend);
2275     }
2276     if (im->second_axis_legend[0] != '\0'){
2277             double Xylabel=gfx_get_text_width(im->canvas, 0,
2278                         im->text_prop[TEXT_PROP_AXIS].font,
2279                         im->text_prop[TEXT_PROP_AXIS].size,
2280                         im->tabwidth,
2281                         "0", 0) * im->unitslength
2282                     + im->text_prop[TEXT_PROP_UNIT].size *2;
2283             gfx_new_text( im->canvas,
2284                   im->xorigin+im->xsize+Xylabel+4, (im->yorigin - im->ysize/2),
2285                   im->graph_col[GRC_FONT],
2286                   im->text_prop[TEXT_PROP_UNIT].font,
2287                   im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth, 
2288                   RRDGRAPH_YLEGEND_ANGLE,
2289                   GFX_H_LEFT, GFX_V_CENTER,
2290                   im->second_axis_legend);
2291     }        
2292     /* graph title */
2293     gfx_new_text( im->canvas,
2294                   im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2295                   im->graph_col[GRC_FONT],
2296                   im->text_prop[TEXT_PROP_TITLE].font,
2297                   im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2298                   GFX_H_CENTER, GFX_V_CENTER,
2299                   im->title);
2300     /* rrdtool 'logo' */
2301     if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2302             gfx_new_text( im->canvas,
2303                   im->ximg-7, 7,
2304                   ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2305                   im->text_prop[TEXT_PROP_AXIS].font,
2306                   5.5, im->tabwidth, 270,
2307                   GFX_H_RIGHT, GFX_V_TOP,
2308                   "RRDTOOL / TOBI OETIKER");
2309     }
2310     /* graph watermark */
2311     if(im->watermark[0] != '\0') {
2312         gfx_new_text( im->canvas,
2313                   im->ximg/2, im->yimg-6,
2314                   ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2315                   im->text_prop[TEXT_PROP_AXIS].font,
2316                   5.5, im->tabwidth, 0,
2317                   GFX_H_CENTER, GFX_V_BOTTOM,
2318                   im->watermark);
2319     }
2320     
2321     /* graph labels */
2322     if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2323             for(i=0;i<im->gdes_c;i++){
2324                     if(im->gdes[i].legend[0] =='\0')
2325                             continue;
2326                     
2327                     /* im->gdes[i].leg_y is the bottom of the legend */
2328                     X0 = im->gdes[i].leg_x;
2329                     Y0 = im->gdes[i].leg_y;
2330                     gfx_new_text ( im->canvas, X0, Y0,
2331                                    im->graph_col[GRC_FONT],
2332                                    im->text_prop[TEXT_PROP_LEGEND].font,
2333                                    im->text_prop[TEXT_PROP_LEGEND].size,
2334                                    im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2335                                    im->gdes[i].legend );
2336                     /* The legend for GRAPH items starts with "M " to have
2337                        enough space for the box */
2338                     if (           im->gdes[i].gf != GF_PRINT &&
2339                                    im->gdes[i].gf != GF_GPRINT &&
2340                                    im->gdes[i].gf != GF_COMMENT) {
2341                             int boxH, boxV;
2342                             
2343                             boxH = gfx_get_text_width(im->canvas, 0,
2344                                                       im->text_prop[TEXT_PROP_LEGEND].font,
2345                                                       im->text_prop[TEXT_PROP_LEGEND].size,
2346                                                       im->tabwidth,"o", 0) * 1.2;
2347                             boxV = boxH*1.1;
2348                             
2349                             /* make sure transparent colors show up the same way as in the graph */
2350                              node = gfx_new_area(im->canvas,
2351                                                 X0,Y0-boxV,
2352                                                 X0,Y0,
2353                                                 X0+boxH,Y0,
2354                                                 im->graph_col[GRC_BACK]);
2355                             gfx_add_point ( node, X0+boxH, Y0-boxV );
2357                             node = gfx_new_area(im->canvas,
2358                                                 X0,Y0-boxV,
2359                                                 X0,Y0,
2360                                                 X0+boxH,Y0,
2361                                                 im->gdes[i].col);
2362                             gfx_add_point ( node, X0+boxH, Y0-boxV );
2363                             node = gfx_new_line(im->canvas,
2364                                                 X0,Y0-boxV,
2365                                                 X0,Y0,
2366                                                 1.0,im->graph_col[GRC_FRAME]);
2367                             gfx_add_point(node,X0+boxH,Y0);
2368                             gfx_add_point(node,X0+boxH,Y0-boxV);
2369                             gfx_close_path(node);
2370                     }
2371             }
2372     }
2376 /*****************************************************
2377  * lazy check make sure we rely need to create this graph
2378  *****************************************************/
2380 int lazy_check(image_desc_t *im){
2381     FILE *fd = NULL;
2382         int size = 1;
2383     struct stat  imgstat;
2384     
2385     if (im->lazy == 0) return 0; /* no lazy option */
2386     if (stat(im->graphfile,&imgstat) != 0) 
2387       return 0; /* can't stat */
2388     /* one pixel in the existing graph is more then what we would
2389        change here ... */
2390     if (time(NULL) - imgstat.st_mtime > 
2391         (im->end - im->start) / im->xsize) 
2392       return 0;
2393     if ((fd = fopen(im->graphfile,"rb")) == NULL) 
2394       return 0; /* the file does not exist */
2395     switch (im->canvas->imgformat) {
2396     case IF_PNG:
2397            size = PngSize(fd,&(im->ximg),&(im->yimg));
2398            break;
2399     default:
2400            size = 1;
2401     }
2402     fclose(fd);
2403     return size;
2406 #ifdef WITH_PIECHART
2407 void
2408 pie_part(image_desc_t *im, gfx_color_t color,
2409             double PieCenterX, double PieCenterY, double Radius,
2410             double startangle, double endangle)
2412     gfx_node_t *node;
2413     double angle;
2414     double step=M_PI/50; /* Number of iterations for the circle;
2415                          ** 10 is definitely too low, more than
2416                          ** 50 seems to be overkill
2417                          */
2419     /* Strange but true: we have to work clockwise or else
2420     ** anti aliasing nor transparency don't work.
2421     **
2422     ** This test is here to make sure we do it right, also
2423     ** this makes the for...next loop more easy to implement.
2424     ** The return will occur if the user enters a negative number
2425     ** (which shouldn't be done according to the specs) or if the
2426     ** programmers do something wrong (which, as we all know, never
2427     ** happens anyway :)
2428     */
2429     if (endangle<startangle) return;
2431     /* Hidden feature: Radius decreases each full circle */
2432     angle=startangle;
2433     while (angle>=2*M_PI) {
2434         angle  -= 2*M_PI;
2435         Radius *= 0.8;
2436     }
2438     node=gfx_new_area(im->canvas,
2439                 PieCenterX+sin(startangle)*Radius,
2440                 PieCenterY-cos(startangle)*Radius,
2441                 PieCenterX,
2442                 PieCenterY,
2443                 PieCenterX+sin(endangle)*Radius,
2444                 PieCenterY-cos(endangle)*Radius,
2445                 color);
2446     for (angle=endangle;angle-startangle>=step;angle-=step) {
2447         gfx_add_point(node,
2448                 PieCenterX+sin(angle)*Radius,
2449                 PieCenterY-cos(angle)*Radius );
2450     }
2453 #endif
2455 int
2456 graph_size_location(image_desc_t *im, int elements
2458 #ifdef WITH_PIECHART
2459 , int piechart
2460 #endif
2462  )
2464     /* The actual size of the image to draw is determined from
2465     ** several sources.  The size given on the command line is
2466     ** the graph area but we need more as we have to draw labels
2467     ** and other things outside the graph area
2468     */
2470     /* +-+-------------------------------------------+
2471     ** |l|.................title.....................|
2472     ** |e+--+-------------------------------+--------+
2473     ** |b| b|                               |        |
2474     ** |a| a|                               |  pie   |
2475     ** |l| l|          main graph area      | chart  |
2476     ** |.| .|                               |  area  |
2477     ** |t| y|                               |        |
2478     ** |r+--+-------------------------------+--------+
2479     ** |e|  | x-axis labels                 |        |
2480     ** |v+--+-------------------------------+--------+
2481     ** | |..............legends......................|
2482     ** +-+-------------------------------------------+
2483     ** |                 watermark                   |
2484     ** +---------------------------------------------+
2485     */
2486     int Xvertical=0,        
2487                         Ytitle   =0,
2488         Xylabel  =0,        
2489         Xmain    =0,        Ymain    =0,
2490 #ifdef WITH_PIECHART
2491         Xpie     =0,        Ypie     =0,
2492 #endif
2493                         Yxlabel  =0,
2494 #if 0
2495         Xlegend  =0,        Ylegend  =0,
2496 #endif
2497         Xspacing =15,  Yspacing =15,
2498        
2499                       Ywatermark =4;
2501     if (im->extra_flags & ONLY_GRAPH) {
2502         im->xorigin =0;
2503         im->ximg = im->xsize;
2504         im->yimg = im->ysize;
2505         im->yorigin = im->ysize;
2506         ytr(im,DNAN); 
2507         return 0;
2508     }
2510     if (im->ylegend[0] != '\0' ) {
2511            Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2512     }
2515     if (im->title[0] != '\0') {
2516         /* The title is placed "inbetween" two text lines so it
2517         ** automatically has some vertical spacing.  The horizontal
2518         ** spacing is added here, on each side.
2519         */
2520         /* don't care for the with of the title
2521                 Xtitle = gfx_get_text_width(im->canvas, 0,
2522                 im->text_prop[TEXT_PROP_TITLE].font,
2523                 im->text_prop[TEXT_PROP_TITLE].size,
2524                 im->tabwidth,
2525                 im->title, 0) + 2*Xspacing; */
2526         Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2527     }
2529     if (elements) {
2530         Xmain=im->xsize;
2531         Ymain=im->ysize;
2532         if (im->draw_x_grid) {
2533             Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2534         }
2535         if (im->draw_y_grid || im->forceleftspace ) {
2536             Xylabel=gfx_get_text_width(im->canvas, 0,
2537                         im->text_prop[TEXT_PROP_AXIS].font,
2538                         im->text_prop[TEXT_PROP_AXIS].size,
2539                         im->tabwidth,
2540                         "0", 0) * im->unitslength;
2541         }
2542     }
2544 #ifdef WITH_PIECHART
2545     if (piechart) {
2546         im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2547         Xpie=im->piesize;
2548         Ypie=im->piesize;
2549     }
2550 #endif
2552     /* Now calculate the total size.  Insert some spacing where
2553        desired.  im->xorigin and im->yorigin need to correspond
2554        with the lower left corner of the main graph area or, if
2555        this one is not set, the imaginary box surrounding the
2556        pie chart area. */
2558     /* The legend width cannot yet be determined, as a result we
2559     ** have problems adjusting the image to it.  For now, we just
2560     ** forget about it at all; the legend will have to fit in the
2561     ** size already allocated.
2562     */
2563     im->ximg = Xylabel + Xmain + 2 * Xspacing;
2565     if (im->second_axis_scale != 0){
2566         im->ximg += Xylabel + Xspacing;
2567     }
2568     if (im->extra_flags & NO_RRDTOOL_TAG){
2569         im->ximg -= Xspacing;
2570     }
2572 #ifdef WITH_PIECHART
2573     im->ximg  += Xpie;
2574 #endif
2576     if (Xmain) im->ximg += Xspacing;
2577 #ifdef WITH_PIECHART
2578     if (Xpie) im->ximg += Xspacing;
2579 #endif
2581     im->xorigin = Xspacing + Xylabel;
2583     /* the length of the title should not influence with width of the graph
2584        if (Xtitle > im->ximg) im->ximg = Xtitle; */
2586     if (Xvertical) { /* unit description */
2587         im->ximg += Xvertical;
2588         im->xorigin += Xvertical;
2589     }
2590     if (im->second_axis_legend[0] != '\0' ) {
2591         im->ximg += Xvertical;
2592     }
2594     xtr(im,0);
2596     /* The vertical size is interesting... we need to compare
2597     ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with 
2598     ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2599     ** in order to start even thinking about Ylegend or Ywatermark.
2600     **
2601     ** Do it in three portions: First calculate the inner part,
2602     ** then do the legend, then adjust the total height of the img,
2603     ** adding space for a watermark if one exists;
2604     */
2606     /* reserve space for main and/or pie */
2608     im->yimg = Ymain + Yxlabel;
2609     
2610 #ifdef WITH_PIECHART
2611     if (im->yimg < Ypie) im->yimg = Ypie;
2612 #endif
2614     im->yorigin = im->yimg - Yxlabel;
2616     /* reserve space for the title *or* some padding above the graph */
2617     if (Ytitle) {
2618         im->yimg += Ytitle;
2619         im->yorigin += Ytitle;
2620     } else {
2621         im->yimg += 1.5*Yspacing;
2622         im->yorigin += 1.5*Yspacing;
2623     }
2624     /* reserve space for padding below the graph */
2625     im->yimg += Yspacing;
2626      
2627     /* Determine where to place the legends onto the image.
2628     ** Adjust im->yimg to match the space requirements.
2629     */
2630     if(leg_place(im)==-1)
2631         return -1;
2632         
2633     if (im->watermark[0] != '\0') {
2634         im->yimg += Ywatermark;
2635     }
2637 #if 0
2638     if (Xlegend > im->ximg) {
2639         im->ximg = Xlegend;
2640         /* reposition Pie */
2641     }
2642 #endif
2644 #ifdef WITH_PIECHART
2645     /* The pie is placed in the upper right hand corner,
2646     ** just below the title (if any) and with sufficient
2647     ** padding.
2648     */
2649     if (elements) {
2650         im->pie_x = im->ximg - Xspacing - Xpie/2;
2651         im->pie_y = im->yorigin-Ymain+Ypie/2;
2652     } else {
2653         im->pie_x = im->ximg/2;
2654         im->pie_y = im->yorigin-Ypie/2;
2655     }
2656 #endif
2658     ytr(im,DNAN);
2659     return 0;
2662 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2663 /* yes we are loosing precision by doing tos with floats instead of doubles
2664    but it seems more stable this way. */
2665    
2667 /* draw that picture thing ... */
2668 int
2669 graph_paint(image_desc_t *im, char ***calcpr)
2671   int i,ii;
2672   int lazy =     lazy_check(im);
2673 #ifdef WITH_PIECHART
2674   int piechart = 0;
2675   double PieStart=0.0;
2676 #endif
2677   FILE  *fo;
2678   gfx_node_t *node;
2679   
2680   double areazero = 0.0;
2681   graph_desc_t *lastgdes = NULL;    
2683   /* if we are lazy and there is nothing to PRINT ... quit now */
2684   if (lazy && im->prt_c==0) return 0;
2686   /* pull the data from the rrd files ... */
2687   
2688   if(data_fetch(im)==-1)
2689     return -1;
2691   /* evaluate VDEF and CDEF operations ... */
2692   if(data_calc(im)==-1)
2693     return -1;
2695 #ifdef WITH_PIECHART  
2696   /* check if we need to draw a piechart */
2697   for(i=0;i<im->gdes_c;i++){
2698     if (im->gdes[i].gf == GF_PART) {
2699       piechart=1;
2700       break;
2701     }
2702   }
2703 #endif
2705   /* calculate and PRINT and GPRINT definitions. We have to do it at
2706    * this point because it will affect the length of the legends
2707    * if there are no graph elements we stop here ... 
2708    * if we are lazy, try to quit ... 
2709    */
2710   i=print_calc(im,calcpr);
2711   if(i<0) return -1;
2712   if(((i==0)
2713 #ifdef WITH_PIECHART
2714 &&(piechart==0)
2715 #endif
2716 ) || lazy) return 0;
2718 #ifdef WITH_PIECHART
2719   /* If there's only the pie chart to draw, signal this */
2720   if (i==0) piechart=2;
2721 #endif
2722   
2723   /* get actual drawing data and find min and max values*/
2724   if(data_proc(im)==-1)
2725     return -1;
2726   
2727   if(!im->logarithmic){si_unit(im);}        /* identify si magnitude Kilo, Mega Giga ? */
2728   
2729   if(!im->rigid && ! im->logarithmic)
2730     expand_range(im);   /* make sure the upper and lower limit are
2731                            sensible values */
2733   if (!calc_horizontal_grid(im))
2734     return -1;
2736   if (im->gridfit)
2737     apply_gridfit(im);
2740 /**************************************************************
2741  *** Calculating sizes and locations became a bit confusing ***
2742  *** so I moved this into a separate function.              ***
2743  **************************************************************/
2744   if(graph_size_location(im,i
2745 #ifdef WITH_PIECHART
2746 ,piechart
2747 #endif
2748 )==-1)
2749     return -1;
2751   /* the actual graph is created by going through the individual
2752      graph elements and then drawing them */
2753   
2754   node=gfx_new_area ( im->canvas,
2755                       0, 0,
2756                       0, im->yimg,
2757                       im->ximg, im->yimg,                      
2758                       im->graph_col[GRC_BACK]);
2760   gfx_add_point(node,im->ximg, 0);
2762 #ifdef WITH_PIECHART
2763   if (piechart != 2) {
2764 #endif
2765     node=gfx_new_area ( im->canvas,
2766                       im->xorigin,             im->yorigin, 
2767                       im->xorigin + im->xsize, im->yorigin,
2768                       im->xorigin + im->xsize, im->yorigin-im->ysize,
2769                       im->graph_col[GRC_CANVAS]);
2770   
2771     gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2773     if (im->minval > 0.0)
2774       areazero = im->minval;
2775     if (im->maxval < 0.0)
2776       areazero = im->maxval;
2777 #ifdef WITH_PIECHART
2778    }
2779 #endif
2781 #ifdef WITH_PIECHART
2782   if (piechart) {
2783     pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2784   }
2785 #endif
2787   for(i=0;i<im->gdes_c;i++){
2788     switch(im->gdes[i].gf){
2789     case GF_CDEF:
2790     case GF_VDEF:
2791     case GF_DEF:
2792     case GF_PRINT:
2793     case GF_GPRINT:
2794     case GF_COMMENT:
2795     case GF_HRULE:
2796     case GF_VRULE:
2797     case GF_XPORT:
2798     case GF_SHIFT:
2799       break;
2800     case GF_TICK:
2801       for (ii = 0; ii < im->xsize; ii++)
2802         {
2803           if (!isnan(im->gdes[i].p_data[ii]) && 
2804               im->gdes[i].p_data[ii] != 0.0)
2805            { 
2806               if (im -> gdes[i].yrule > 0 ) {
2807                       gfx_new_line(im->canvas,
2808                                    im -> xorigin + ii, im->yorigin,
2809                                    im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2810                                    1.0,
2811                                    im -> gdes[i].col );
2812               } else if ( im -> gdes[i].yrule < 0 ) {
2813                       gfx_new_line(im->canvas,
2814                                    im -> xorigin + ii, im->yorigin - im->ysize,
2815                                    im -> xorigin + ii, im->yorigin - im->ysize - im->gdes[i].yrule * im->ysize,
2816                                    1.0,
2817                                    im -> gdes[i].col );
2818               
2819               }
2820            }
2821         }
2822       break;
2823     case GF_LINE:
2824     case GF_AREA:
2825       /* fix data points at oo and -oo */
2826       for(ii=0;ii<im->xsize;ii++){
2827         if (isinf(im->gdes[i].p_data[ii])){
2828           if (im->gdes[i].p_data[ii] > 0) {
2829             im->gdes[i].p_data[ii] = im->maxval ;
2830           } else {
2831             im->gdes[i].p_data[ii] = im->minval ;
2832           }                 
2833           
2834         }
2835       } /* for */
2837       /* *******************************************************
2838        a           ___. (a,t) 
2839                     |   |    ___
2840               ____|   |   |   |
2841               |       |___|
2842        -------|--t-1--t--------------------------------      
2843                       
2844       if we know the value at time t was a then 
2845       we draw a square from t-1 to t with the value a.
2847       ********************************************************* */
2848       if (im->gdes[i].col != 0x0){   
2849         /* GF_LINE and friend */
2850         if(im->gdes[i].gf == GF_LINE ){
2851           double last_y=0.0;
2852           node = NULL;
2853           for(ii=1;ii<im->xsize;ii++){
2854             if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2855                 node = NULL;
2856                 continue;
2857             }
2858             if ( node == NULL ) {
2859                      last_y = ytr(im,im->gdes[i].p_data[ii]);
2860                 if ( im->slopemode == 0 ){
2861                   node = gfx_new_line(im->canvas,
2862                                     ii-1+im->xorigin,last_y,
2863                                     ii+im->xorigin,last_y,
2864                                     im->gdes[i].linewidth,
2865                                     im->gdes[i].col);
2866                 } else {
2867                   node = gfx_new_line(im->canvas,
2868                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2869                                     ii+im->xorigin,last_y,
2870                                     im->gdes[i].linewidth,
2871                                     im->gdes[i].col);
2872                 }
2873              } else {
2874                double new_y = ytr(im,im->gdes[i].p_data[ii]);
2875                if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2876                    gfx_add_point(node,ii-1+im->xorigin,new_y);
2877                };
2878                last_y = new_y;
2879                gfx_add_point(node,ii+im->xorigin,new_y);
2880              };
2882           }
2883         } else {
2884           int idxI=-1;
2885           double *foreY=malloc(sizeof(double)*im->xsize*2);
2886           double *foreX=malloc(sizeof(double)*im->xsize*2);
2887           double *backY=malloc(sizeof(double)*im->xsize*2);
2888           double *backX=malloc(sizeof(double)*im->xsize*2);
2889           int drawem = 0;
2890           for(ii=0;ii<=im->xsize;ii++){
2891             double ybase,ytop;
2892             if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2893                int cntI=1;
2894                int lastI=0;
2895                while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2896                node = gfx_new_area(im->canvas,
2897                                 backX[0],backY[0],
2898                                 foreX[0],foreY[0],
2899                                 foreX[cntI],foreY[cntI], im->gdes[i].col);
2900                while (cntI < idxI) {
2901                  lastI = cntI;
2902                  cntI++;
2903                  while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;} 
2904                  gfx_add_point(node,foreX[cntI],foreY[cntI]);
2905                }
2906                gfx_add_point(node,backX[idxI],backY[idxI]);
2907                while (idxI > 1){
2908                  lastI = idxI;
2909                  idxI--;
2910                  while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;} 
2911                  gfx_add_point(node,backX[idxI],backY[idxI]);
2912                }
2913                idxI=-1;
2914                drawem = 0;
2915             }
2916             if (drawem != 0){
2917               drawem = 0;
2918               idxI=-1;
2919             }
2920             if (ii == im->xsize) break;
2921             
2922             /* keep things simple for now, just draw these bars
2923                do not try to build a big and complex area */
2925                                                                
2926             if ( im->slopemode == 0 && ii==0){
2927                 continue;
2928             }
2929             if ( isnan(im->gdes[i].p_data[ii]) ) {
2930                 drawem = 1;
2931                 continue;
2932             }
2933             ytop = ytr(im,im->gdes[i].p_data[ii]);
2934              if ( lastgdes && im->gdes[i].stack ) {
2935                   ybase = ytr(im,lastgdes->p_data[ii]);
2936             } else {
2937                   ybase = ytr(im,areazero);
2938             }
2939             if ( ybase == ytop ){
2940                 drawem = 1;
2941                 continue;        
2942             }
2943             /* every area has to be wound clock-wise,
2944                so we have to make sur base remains base  */                
2945             if (ybase > ytop){
2946                 double extra = ytop;
2947                 ytop = ybase;
2948                 ybase = extra;
2949             }
2950             if ( im->slopemode == 0 ){
2951                     backY[++idxI] = ybase-0.2;
2952                     backX[idxI] = ii+im->xorigin-1;
2953                     foreY[idxI] = ytop+0.2;
2954                     foreX[idxI] = ii+im->xorigin-1;
2955             }
2956             backY[++idxI] = ybase-0.2;
2957             backX[idxI] = ii+im->xorigin;
2958             foreY[idxI] = ytop+0.2;
2959             foreX[idxI] = ii+im->xorigin;
2960           }
2961           /* close up any remaining area */             
2962           free(foreY);
2963           free(foreX);
2964           free(backY);
2965           free(backX);
2966         } /* else GF_LINE */
2967       } /* if color != 0x0 */
2968       /* make sure we do not run into trouble when stacking on NaN */
2969       for(ii=0;ii<im->xsize;ii++){
2970         if (isnan(im->gdes[i].p_data[ii])) {
2971           if (lastgdes && (im->gdes[i].stack)) {
2972             im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2973           } else {
2974             im->gdes[i].p_data[ii] = areazero;
2975           }
2976         }
2977       } 
2978       lastgdes = &(im->gdes[i]);                         
2979       break;
2980 #ifdef WITH_PIECHART
2981     case GF_PART:
2982       if(isnan(im->gdes[i].yrule)) /* fetch variable */
2983         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2984      
2985       if (finite(im->gdes[i].yrule)) {        /* even the fetched var can be NaN */
2986         pie_part(im,im->gdes[i].col,
2987                 im->pie_x,im->pie_y,im->piesize*0.4,
2988                 M_PI*2.0*PieStart/100.0,
2989                 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2990         PieStart += im->gdes[i].yrule;
2991       }
2992       break;
2993 #endif
2994     case GF_STACK:
2995       rrd_set_error("STACK should already be turned into LINE or AREA here");
2996       return -1;
2997       break;
2998         
2999     } /* switch */
3000   }
3001 #ifdef WITH_PIECHART
3002   if (piechart==2) {
3003     im->draw_x_grid=0;
3004     im->draw_y_grid=0;
3005   }
3006 #endif
3009   /* grid_paint also does the text */
3010   if( !(im->extra_flags & ONLY_GRAPH) )  
3011     grid_paint(im);
3013   
3014   if( !(im->extra_flags & ONLY_GRAPH) )  
3015       axis_paint(im);
3016   
3017   /* the RULES are the last thing to paint ... */
3018   for(i=0;i<im->gdes_c;i++){    
3019     
3020     switch(im->gdes[i].gf){
3021     case GF_HRULE:
3022       if(im->gdes[i].yrule >= im->minval
3023          && im->gdes[i].yrule <= im->maxval)
3024         gfx_new_line(im->canvas,
3025                      im->xorigin,ytr(im,im->gdes[i].yrule),
3026                      im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
3027                      1.0,im->gdes[i].col); 
3028       break;
3029     case GF_VRULE:
3030       if(im->gdes[i].xrule >= im->start
3031          && im->gdes[i].xrule <= im->end)
3032         gfx_new_line(im->canvas,
3033                      xtr(im,im->gdes[i].xrule),im->yorigin,
3034                      xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
3035                      1.0,im->gdes[i].col); 
3036       break;
3037     default:
3038       break;
3039     }
3040   }
3042   
3043   if (strcmp(im->graphfile,"-")==0) {
3044     fo = im->graphhandle ? im->graphhandle : stdout;
3045 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3046     /* Change translation mode for stdout to BINARY */
3047     _setmode( _fileno( fo ), O_BINARY );
3048 #endif
3049   } else {
3050     if ((fo = fopen(im->graphfile,"wb")) == NULL) {
3051       rrd_set_error("Opening '%s' for write: %s",im->graphfile,
3052                     rrd_strerror(errno));
3053       return (-1);
3054     }
3055   }
3056   gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
3057   if (strcmp(im->graphfile,"-") != 0)
3058     fclose(fo);
3059   return 0;
3063 /*****************************************************
3064  * graph stuff 
3065  *****************************************************/
3067 int
3068 gdes_alloc(image_desc_t *im){
3070     im->gdes_c++;
3071     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3072                                            * sizeof(graph_desc_t)))==NULL){
3073         rrd_set_error("realloc graph_descs");
3074         return -1;
3075     }
3078     im->gdes[im->gdes_c-1].step=im->step;
3079     im->gdes[im->gdes_c-1].step_orig=im->step;
3080     im->gdes[im->gdes_c-1].stack=0;
3081     im->gdes[im->gdes_c-1].linewidth=0;
3082     im->gdes[im->gdes_c-1].debug=0;
3083     im->gdes[im->gdes_c-1].start=im->start; 
3084     im->gdes[im->gdes_c-1].start_orig=im->start; 
3085     im->gdes[im->gdes_c-1].end=im->end; 
3086     im->gdes[im->gdes_c-1].end_orig=im->end; 
3087     im->gdes[im->gdes_c-1].vname[0]='\0'; 
3088     im->gdes[im->gdes_c-1].data=NULL;
3089     im->gdes[im->gdes_c-1].ds_namv=NULL;
3090     im->gdes[im->gdes_c-1].data_first=0;
3091     im->gdes[im->gdes_c-1].p_data=NULL;
3092     im->gdes[im->gdes_c-1].rpnp=NULL;
3093     im->gdes[im->gdes_c-1].shift=0;
3094     im->gdes[im->gdes_c-1].col = 0x0;
3095     im->gdes[im->gdes_c-1].legend[0]='\0';
3096     im->gdes[im->gdes_c-1].format[0]='\0';
3097     im->gdes[im->gdes_c-1].strftm=0;   
3098     im->gdes[im->gdes_c-1].rrd[0]='\0';
3099     im->gdes[im->gdes_c-1].ds=-1;    
3100     im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;    
3101     im->gdes[im->gdes_c-1].cf=CF_AVERAGE;    
3102     im->gdes[im->gdes_c-1].p_data=NULL;    
3103     im->gdes[im->gdes_c-1].yrule=DNAN;
3104     im->gdes[im->gdes_c-1].xrule=0;
3105     return 0;
3108 /* copies input untill the first unescaped colon is found
3109    or until input ends. backslashes have to be escaped as well */
3110 int
3111 scan_for_col(const char *const input, int len, char *const output)
3113     int inp,outp=0;
3114     for (inp=0; 
3115          inp < len &&
3116            input[inp] != ':' &&
3117            input[inp] != '\0';
3118          inp++){
3119       if (input[inp] == '\\' &&
3120           input[inp+1] != '\0' && 
3121           (input[inp+1] == '\\' ||
3122            input[inp+1] == ':')){
3123         output[outp++] = input[++inp];
3124       }
3125       else {
3126         output[outp++] = input[inp];
3127       }
3128     }
3129     output[outp] = '\0';
3130     return inp;
3132 /* Some surgery done on this function, it became ridiculously big.
3133 ** Things moved:
3134 ** - initializing     now in rrd_graph_init()
3135 ** - options parsing  now in rrd_graph_options()
3136 ** - script parsing   now in rrd_graph_script()
3137 */
3138 int 
3139 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
3141     image_desc_t   im;
3142     rrd_graph_init(&im);
3143     im.graphhandle = stream;
3144     
3145     rrd_graph_options(argc,argv,&im);
3146     if (rrd_test_error()) {
3147         im_free(&im);
3148         return -1;
3149     }
3150     
3151     if (strlen(argv[optind])>=MAXPATH) {
3152         rrd_set_error("filename (including path) too long");
3153         im_free(&im);
3154         return -1;
3155     }
3156     strncpy(im.graphfile,argv[optind],MAXPATH-1);
3157     im.graphfile[MAXPATH-1]='\0';
3159     rrd_graph_script(argc,argv,&im,1);
3160     if (rrd_test_error()) {
3161         im_free(&im);
3162         return -1;
3163     }
3165     /* Everything is now read and the actual work can start */
3167     (*prdata)=NULL;
3168     if (graph_paint(&im,prdata)==-1){
3169         im_free(&im);
3170         return -1;
3171     }
3173     /* The image is generated and needs to be output.
3174     ** Also, if needed, print a line with information about the image.
3175     */
3177     *xsize=im.ximg;
3178     *ysize=im.yimg;
3179     *ymin=im.minval;
3180     *ymax=im.maxval;
3181     if (im.imginfo) {
3182         char *filename;
3183         char *path;
3184         if (!(*prdata)) {
3185             /* maybe prdata is not allocated yet ... lets do it now */
3186             if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
3187                 rrd_set_error("malloc imginfo");
3188                 return -1; 
3189             };
3190         }
3191         if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3192          ==NULL){
3193             rrd_set_error("malloc imginfo");
3194             return -1;
3195         }
3196         path = strdup(im.graphfile);
3197         filename = basename(path);
3198         sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
3199         free(path);
3200     }
3201     im_free(&im);
3202     return 0;
3205 void
3206 rrd_graph_init(image_desc_t *im)
3208     unsigned int i;
3210 #ifdef HAVE_TZSET
3211     tzset();
3212 #endif
3213 #ifdef HAVE_SETLOCALE
3214     setlocale(LC_TIME,"");
3215 #ifdef HAVE_MBSTOWCS
3216     setlocale(LC_CTYPE,"");
3217 #endif
3218 #endif
3219     im->yorigin=0;
3220     im->xorigin=0;
3221     im->minval=0;
3222     im->xlab_user.minsec = -1;
3223     im->ximg=0;
3224     im->yimg=0;
3225     im->xsize = 400;
3226     im->ysize = 100;
3227     im->step = 0;
3228     im->ylegend[0] = '\0';
3229     im->second_axis_scale = 0; /* 0 disables it */
3230     im->second_axis_shift = 0; /* no shift by default */
3231     im->second_axis_legend[0] = '\0';
3232     im->second_axis_format[0] = '\0';
3233     im->title[0] = '\0';
3234     im->watermark[0] = '\0';
3235     im->minval = DNAN;
3236     im->maxval = DNAN;    
3237     im->unitsexponent= 9999;
3238     im->unitslength= 6; 
3239     im->forceleftspace = 0;
3240     im->symbol = ' ';
3241     im->viewfactor = 1.0;
3242     im->extra_flags= 0;
3243     im->rigid = 0;
3244     im->gridfit = 1;
3245     im->imginfo = NULL;
3246     im->lazy = 0;
3247     im->slopemode = 0;
3248     im->logarithmic = 0;
3249     im->ygridstep = DNAN;
3250     im->draw_x_grid = 1;
3251     im->draw_y_grid = 1;
3252     im->base = 1000;
3253     im->prt_c = 0;
3254     im->gdes_c = 0;
3255     im->gdes = NULL;
3256     im->canvas = gfx_new_canvas();
3257     im->grid_dash_on = 1;
3258     im->grid_dash_off = 1;
3259     im->tabwidth = 40.0;
3260     
3261     for(i=0;i<DIM(graph_col);i++)
3262         im->graph_col[i]=graph_col[i];
3264 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3265     {
3266             char *windir; 
3267             char rrd_win_default_font[1000];
3268             windir = getenv("windir");
3269             /* %windir% is something like D:\windows or C:\winnt */
3270             if (windir != NULL) {
3271                     strncpy(rrd_win_default_font,windir,500);
3272                     rrd_win_default_font[500] = '\0';
3273                     strcat(rrd_win_default_font,"\\fonts\\");
3274                     strcat(rrd_win_default_font,RRD_DEFAULT_FONT);         
3275                     for(i=0;i<DIM(text_prop);i++){
3276                             strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3277                             text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3278                      }
3279              }
3280     }
3281 #endif
3282     {
3283             char *deffont; 
3284             deffont = getenv("RRD_DEFAULT_FONT");
3285             if (deffont != NULL) {
3286                  for(i=0;i<DIM(text_prop);i++){
3287                         strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3288                         text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3289                  }
3290             }
3291     }
3292     for(i=0;i<DIM(text_prop);i++){        
3293       im->text_prop[i].size = text_prop[i].size;
3294       strcpy(im->text_prop[i].font,text_prop[i].font);
3295     }
3298 void
3299 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3301     int                        stroff;    
3302     char                *parsetime_error = NULL;
3303     char                scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3304     time_t                start_tmp=0,end_tmp=0;
3305     long                long_tmp;
3306     struct rrd_time_value        start_tv, end_tv;
3307     gfx_color_t         color;
3308     optind = 0; opterr = 0;  /* initialize getopt */
3310     parsetime("end-24h", &start_tv);
3311     parsetime("now", &end_tv);
3313     /* defines for long options without a short equivalent. should be bytes,
3314        and may not collide with (the ASCII value of) short options */
3315     #define LONGOPT_UNITS_SI 255
3317     while (1){
3318         static struct option long_options[] =
3319         {
3320             {"start",      required_argument, 0,  's'},
3321             {"end",        required_argument, 0,  'e'},
3322             {"x-grid",     required_argument, 0,  'x'},
3323             {"y-grid",     required_argument, 0,  'y'},
3324             {"vertical-label",required_argument,0,'v'},
3325             {"width",      required_argument, 0,  'w'},
3326             {"height",     required_argument, 0,  'h'},
3327             {"interlaced", no_argument,       0,  'i'},
3328             {"upper-limit",required_argument, 0,  'u'},
3329             {"lower-limit",required_argument, 0,  'l'},
3330             {"rigid",      no_argument,       0,  'r'},
3331             {"base",       required_argument, 0,  'b'},
3332             {"logarithmic",no_argument,       0,  'o'},
3333             {"color",      required_argument, 0,  'c'},
3334             {"font",       required_argument, 0,  'n'},
3335             {"title",      required_argument, 0,  't'},
3336             {"imginfo",    required_argument, 0,  'f'},
3337             {"imgformat",  required_argument, 0,  'a'},
3338             {"lazy",       no_argument,       0,  'z'},
3339             {"zoom",       required_argument, 0,  'm'},
3340             {"no-legend",  no_argument,       0,  'g'},
3341             {"force-rules-legend",no_argument,0,  'F'},
3342             {"only-graph", no_argument,       0,  'j'},
3343             {"alt-y-grid", no_argument,       0,  'Y'},
3344             {"no-minor",   no_argument,       0,  'I'},
3345             {"slope-mode", no_argument,              0,  'E'},
3346             {"alt-autoscale", no_argument,    0,  'A'},
3347             {"alt-autoscale-min", no_argument, 0, 'J'},
3348             {"alt-autoscale-max", no_argument, 0, 'M'},
3349             {"no-gridfit", no_argument,       0,   'N'},
3350             {"units-exponent",required_argument, 0, 'X'},
3351             {"units-length",required_argument, 0, 'L'},
3352             {"units",      required_argument, 0,  LONGOPT_UNITS_SI },
3353             {"step",       required_argument, 0,    'S'},
3354             {"tabwidth",   required_argument, 0,    'T'},            
3355             {"font-render-mode", required_argument, 0, 'R'},
3356             {"font-smoothing-threshold", required_argument, 0, 'B'},
3357             {"watermark",  required_argument, 0,  'W'},
3358             {"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 */
3359             {"disable-rrdtool-tag", no_argument,  0,  1001},
3360             {"right-axis", required_argument,  0,  1002},
3361             {"right-axis-label", required_argument,  0,  1003},
3362             {"right-axis-format", required_argument,  0,  1004},
3363             {0,0,0,0}};
3364         int option_index = 0;
3365         int opt;
3366         int col_start,col_end;
3368         opt = getopt_long(argc, argv, 
3369                          "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3370                           long_options, &option_index);
3372         if (opt == EOF)
3373             break;
3374         
3375         switch(opt) {
3376         case 'I':
3377             im->extra_flags |= NOMINOR;
3378             break;
3379         case 'Y':
3380             im->extra_flags |= ALTYGRID;
3381             break;
3382         case 'A':
3383             im->extra_flags |= ALTAUTOSCALE;
3384             break;
3385         case 'J':
3386             im->extra_flags |= ALTAUTOSCALE_MIN;
3387             break;
3388         case 'M':
3389             im->extra_flags |= ALTAUTOSCALE_MAX;
3390             break;
3391         case 'j':
3392            im->extra_flags |= ONLY_GRAPH;
3393            break;
3394         case 'g':
3395             im->extra_flags |= NOLEGEND;
3396             break;
3397         case 'F':
3398             im->extra_flags |= FORCE_RULES_LEGEND;
3399             break;
3400         case 1001:
3401             im->extra_flags |= NO_RRDTOOL_TAG;
3402             break;
3403         case LONGOPT_UNITS_SI:
3404             if(im->extra_flags & FORCE_UNITS) {
3405                 rrd_set_error("--units can only be used once!");
3406                 return;
3407             }
3408             if(strcmp(optarg,"si")==0)
3409                 im->extra_flags |= FORCE_UNITS_SI;
3410             else {
3411                 rrd_set_error("invalid argument for --units: %s", optarg );
3412                 return;
3413             }
3414             break;
3415         case 'X':
3416             im->unitsexponent = atoi(optarg);
3417             break;
3418         case 'L':
3419             im->unitslength = atoi(optarg);
3420             im->forceleftspace = 1;
3421             break;
3422         case 'T':
3423             im->tabwidth = atof(optarg);
3424             break;
3425         case 'S':
3426             im->step =  atoi(optarg);
3427             break;
3428         case 'N':
3429             im->gridfit = 0;
3430             break;
3431         case 's':
3432             if ((parsetime_error = parsetime(optarg, &start_tv))) {
3433                 rrd_set_error( "start time: %s", parsetime_error );
3434                 return;
3435             }
3436             break;
3437         case 'e':
3438             if ((parsetime_error = parsetime(optarg, &end_tv))) {
3439                 rrd_set_error( "end time: %s", parsetime_error );
3440                 return;
3441             }
3442             break;
3443         case 'x':
3444             if(strcmp(optarg,"none") == 0){
3445               im->draw_x_grid=0;
3446               break;
3447             };
3448                 
3449             if(sscanf(optarg,
3450                       "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3451                       scan_gtm,
3452                       &im->xlab_user.gridst,
3453                       scan_mtm,
3454                       &im->xlab_user.mgridst,
3455                       scan_ltm,
3456                       &im->xlab_user.labst,
3457                       &im->xlab_user.precis,
3458                       &stroff) == 7 && stroff != 0){
3459                 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3460                 im->xlab_form[sizeof(im->xlab_form)-1] = '\0'; 
3461                 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3462                     rrd_set_error("unknown keyword %s",scan_gtm);
3463                     return;
3464                 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3465                     rrd_set_error("unknown keyword %s",scan_mtm);
3466                     return;
3467                 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3468                     rrd_set_error("unknown keyword %s",scan_ltm);
3469                     return;
3470                 } 
3471                 im->xlab_user.minsec = 1;
3472                 im->xlab_user.stst = im->xlab_form;
3473             } else {
3474                 rrd_set_error("invalid x-grid format");
3475                 return;
3476             }
3477             break;
3478         case 'y':
3480             if(strcmp(optarg,"none") == 0){
3481               im->draw_y_grid=0;
3482               break;
3483             };
3485             if(sscanf(optarg,
3486                       "%lf:%d",
3487                       &im->ygridstep,
3488                       &im->ylabfact) == 2) {
3489                 if(im->ygridstep<=0){
3490                     rrd_set_error("grid step must be > 0");
3491                     return;
3492                 } else if (im->ylabfact < 1){
3493                     rrd_set_error("label factor must be > 0");
3494                     return;
3495                 } 
3496             } else {
3497                 rrd_set_error("invalid y-grid format");
3498                 return;
3499             }
3500             break;
3501         case 1002: /* right y axis */
3503             if(sscanf(optarg,
3504                       "%lf:%lf",
3505                       &im->second_axis_scale,
3506                       &im->second_axis_shift) == 2) {
3507                 if(im->second_axis_scale==0){
3508                     rrd_set_error("the second_axis_scale  must not be 0");
3509                     return;
3510                 }
3511             } else {
3512                 rrd_set_error("invalid right-axis format expected scale:shift");
3513                 return;
3514             }
3515             break;
3516         case 1003:
3517             strncpy(im->second_axis_legend,optarg,150);
3518             im->second_axis_legend[150]='\0';
3519             break;
3520         case 1004:
3521             if (bad_format(optarg)){
3522                 rrd_set_error("use either %le or %lf formats");
3523                 return;
3524             }
3525             strncpy(im->second_axis_format,optarg,150);
3526             im->second_axis_format[150]='\0';
3527             break;
3528         case 'v':
3529             strncpy(im->ylegend,optarg,150);
3530             im->ylegend[150]='\0';
3531             break;
3532         case 'u':
3533             im->maxval = atof(optarg);
3534             break;
3535         case 'l':
3536             im->minval = atof(optarg);
3537             break;
3538         case 'b':
3539             im->base = atol(optarg);
3540             if(im->base != 1024 && im->base != 1000 ){
3541                 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3542                 return;
3543             }
3544             break;
3545         case 'w':
3546             long_tmp = atol(optarg);
3547             if (long_tmp < 10) {
3548                 rrd_set_error("width below 10 pixels");
3549                 return;
3550             }
3551             im->xsize = long_tmp;
3552             break;
3553         case 'h':
3554             long_tmp = atol(optarg);
3555             if (long_tmp < 10) {
3556                 rrd_set_error("height below 10 pixels");
3557                 return;
3558             }
3559             im->ysize = long_tmp;
3560             break;
3561         case 'i':
3562             im->canvas->interlaced = 1;
3563             break;
3564         case 'r':
3565             im->rigid = 1;
3566             break;
3567         case 'f':
3568             im->imginfo = optarg;
3569             break;
3570             case 'a':
3571             if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3572                 rrd_set_error("unsupported graphics format '%s'",optarg);
3573                 return;
3574             }
3575             break;
3576         case 'z':
3577             im->lazy = 1;
3578             break;
3579         case 'E':
3580             im->slopemode = 1;
3581             break;
3583         case 'o':
3584             im->logarithmic = 1;
3585             break;
3586         case 'c':
3587             if(sscanf(optarg,
3588                       "%10[A-Z]#%n%8lx%n",
3589                       col_nam,&col_start,&color,&col_end) == 2){
3590                 int ci;
3591                 int col_len = col_end - col_start;
3592                 switch (col_len){
3593                         case 3:
3594                                 color = (
3595                                         ((color & 0xF00) * 0x110000) |
3596                                         ((color & 0x0F0) * 0x011000) |
3597                                         ((color & 0x00F) * 0x001100) |
3598                                         0x000000FF
3599                                         );
3600                                 break;
3601                         case 4:
3602                                 color = (
3603                                         ((color & 0xF000) * 0x11000) |
3604                                         ((color & 0x0F00) * 0x01100) |
3605                                         ((color & 0x00F0) * 0x00110) |
3606                                         ((color & 0x000F) * 0x00011)
3607                                         );
3608                                 break;
3609                         case 6:
3610                                 color = (color << 8) + 0xff /* shift left by 8 */;
3611                                 break;
3612                         case 8:
3613                                 break;
3614                         default:
3615                                 rrd_set_error("the color format is #RRGGBB[AA]");
3616                                 return;
3617                 }
3618                 if((ci=grc_conv(col_nam)) != -1){
3619                     im->graph_col[ci]=color;
3620                 }  else {
3621                   rrd_set_error("invalid color name '%s'",col_nam);
3622                   return;
3623                 }
3624             } else {
3625                 rrd_set_error("invalid color def format");
3626                 return;
3627             }
3628             break;        
3629         case 'n':{
3630             char prop[15];
3631             double size = 1;
3632             int end;
3633             if(sscanf(optarg,
3634                                 "%10[A-Z]:%lf%n",
3635                                 prop,&size,&end) >= 2){                                
3636                 int sindex,propidx;
3637                 if((sindex=text_prop_conv(prop)) != -1){
3638                   for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){                        
3639                         if (size > 0){
3640                               im->text_prop[propidx].size=size;              
3641                       }        
3642                         if ((int)strlen(optarg) > end+2){
3643                           if (optarg[end] == ':'){
3644                              strncpy(im->text_prop[propidx].font,optarg+end+1,255);
3645                              im->text_prop[propidx].font[255] = '\0';
3646                           } else {
3647                              rrd_set_error("expected : after font size in '%s'",optarg);
3648                             return;
3649                           }
3650                       }
3651                       /* only run the for loop for DEFAULT (0) for
3652                          all others, we break here. woodo programming */
3653                       if (propidx==sindex && sindex != 0) break;
3654                   }
3655                 } else {
3656                     rrd_set_error("invalid fonttag '%s'",prop);
3657                     return;
3658                 }
3659             } else {
3660                 rrd_set_error("invalid text property format");
3661                 return;
3662             }
3663             break;          
3664         }
3665         case 'm':
3666             im->canvas->zoom = atof(optarg);
3667             if (im->canvas->zoom <= 0.0) {
3668                 rrd_set_error("zoom factor must be > 0");
3669                 return;
3670             }
3671           break;
3672         case 't':
3673             strncpy(im->title,optarg,150);
3674             im->title[150]='\0';
3675             break;
3677         case 'R':
3678                 if ( strcmp( optarg, "normal" ) == 0 )
3679                         im->canvas->aa_type = AA_NORMAL;
3680                 else if ( strcmp( optarg, "light" ) == 0 )
3681                         im->canvas->aa_type = AA_LIGHT;
3682                 else if ( strcmp( optarg, "mono" ) == 0 )
3683                         im->canvas->aa_type = AA_NONE;
3684                 else
3685                 {
3686                         rrd_set_error("unknown font-render-mode '%s'", optarg );
3687                         return;
3688                 }
3689                 break;
3691         case 'B':
3692             im->canvas->font_aa_threshold = atof(optarg);
3693                 break;
3695         case 'W':
3696             strncpy(im->watermark,optarg,100);
3697             im->watermark[99]='\0';
3698             break;
3700         case '?':
3701             if (optopt != 0)
3702                 rrd_set_error("unknown option '%c'", optopt);
3703             else
3704                 rrd_set_error("unknown option '%s'",argv[optind-1]);
3705             return;
3706         }
3707     }
3708     
3709     if (optind >= argc) {
3710        rrd_set_error("missing filename");
3711        return;
3712     }
3714     if (im->logarithmic == 1 && im->minval <= 0){
3715         rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");        
3716         return;
3717     }
3719     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3720         /* error string is set in parsetime.c */
3721         return;
3722     }  
3723     
3724     if (start_tmp < 3600*24*365*10){
3725         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3726         return;
3727     }
3728     
3729     if (end_tmp < start_tmp) {
3730         rrd_set_error("start (%ld) should be less than end (%ld)", 
3731                start_tmp, end_tmp);
3732         return;
3733     }
3734     
3735     im->start = start_tmp;
3736     im->end = end_tmp;
3737     im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3740 int
3741 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3743     char *color;
3744     graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3746     color=strstr(var,"#");
3747     if (color==NULL) {
3748         if (optional==0) {
3749             rrd_set_error("Found no color in %s",err);
3750             return 0;
3751         }
3752         return 0;
3753     } else {
3754         int n=0;
3755         char *rest;
3756         gfx_color_t    col;
3758         rest=strstr(color,":");
3759         if (rest!=NULL)
3760             n=rest-color;
3761         else
3762             n=strlen(color);
3764         switch (n) {
3765             case 7:
3766                 sscanf(color,"#%6lx%n",&col,&n);
3767                 col = (col << 8) + 0xff /* shift left by 8 */;
3768                 if (n!=7) rrd_set_error("Color problem in %s",err);
3769                 break;
3770             case 9:
3771                 sscanf(color,"#%8lx%n",&col,&n);
3772                 if (n==9) break;
3773             default:
3774                 rrd_set_error("Color problem in %s",err);
3775         }
3776         if (rrd_test_error()) return 0;
3777         gdp->col = col;
3778         return n;
3779     }
3783 int bad_format(char *fmt) {
3784     char *ptr;
3785     int n=0;
3786     ptr = fmt;
3787     while (*ptr != '\0')
3788         if (*ptr++ == '%') {
3789  
3790              /* line cannot end with percent char */
3791              if (*ptr == '\0') return 1;
3792  
3793              /* '%s', '%S' and '%%' are allowed */
3794              if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3796              /* %c is allowed (but use only with vdef!) */
3797              else if (*ptr == 'c') {
3798                 ptr++;
3799                 n=1;
3800              }
3802              /* or else '% 6.2lf' and such are allowed */
3803              else {
3804                  /* optional padding character */
3805                  if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3807                  /* This should take care of 'm.n' with all three optional */
3808                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3809                  if (*ptr == '.') ptr++;
3810                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3811   
3812                  /* Either 'le', 'lf' or 'lg' must follow here */
3813                  if (*ptr++ != 'l') return 1;
3814                  if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3815                  else return 1;
3816                  n++;
3817             }
3818          }
3819       
3820       return (n!=1); 
3824 int
3825 vdef_parse(gdes,str)
3826 struct graph_desc_t *gdes;
3827 const char *const str;
3829     /* A VDEF currently is either "func" or "param,func"
3830      * so the parsing is rather simple.  Change if needed.
3831      */
3832     double        param;
3833     char        func[30];
3834     int                n;
3835     
3836     n=0;
3837     sscanf(str,"%le,%29[A-Z]%n",&param,func,&n);
3838     if (n== (int)strlen(str)) { /* matched */
3839         ;
3840     } else {
3841         n=0;
3842         sscanf(str,"%29[A-Z]%n",func,&n);
3843         if (n== (int)strlen(str)) { /* matched */
3844             param=DNAN;
3845         } else {
3846             rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3847                 ,str
3848                 ,gdes->vname
3849                 );
3850             return -1;
3851         }
3852     }
3853     if                (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3854     else if        (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3855     else if        (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3856     else if        (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3857     else if        (!strcmp("TOTAL",  func)) gdes->vf.op = VDEF_TOTAL;
3858     else if        (!strcmp("FIRST",  func)) gdes->vf.op = VDEF_FIRST;
3859     else if        (!strcmp("LAST",   func)) gdes->vf.op = VDEF_LAST;
3860     else if     (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3861     else if     (!strcmp("LSLINT",   func)) gdes->vf.op = VDEF_LSLINT;
3862     else if     (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3863     else {
3864         rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3865             ,func
3866             ,gdes->vname
3867             );
3868         return -1;
3869     };
3871     switch (gdes->vf.op) {
3872         case VDEF_PERCENT:
3873             if (isnan(param)) { /* no parameter given */
3874                 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3875                     ,func
3876                     ,gdes->vname
3877                     );
3878                 return -1;
3879             };
3880             if (param>=0.0 && param<=100.0) {
3881                 gdes->vf.param = param;
3882                 gdes->vf.val   = DNAN;        /* undefined */
3883                 gdes->vf.when  = 0;        /* undefined */
3884             } else {
3885                 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3886                     ,param
3887                     ,gdes->vname
3888                     );
3889                 return -1;
3890             };
3891             break;
3892         case VDEF_MAXIMUM:
3893         case VDEF_AVERAGE:
3894         case VDEF_MINIMUM:
3895         case VDEF_TOTAL:
3896         case VDEF_FIRST:
3897         case VDEF_LAST:
3898         case VDEF_LSLSLOPE:
3899         case VDEF_LSLINT:
3900         case VDEF_LSLCORREL:
3901             if (isnan(param)) {
3902                 gdes->vf.param = DNAN;
3903                 gdes->vf.val   = DNAN;
3904                 gdes->vf.when  = 0;
3905             } else {
3906                 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3907                     ,func
3908                     ,gdes->vname
3909                     );
3910                 return -1;
3911             };
3912             break;
3913     };
3914     return 0;
3918 int
3919 vdef_calc(im,gdi)
3920 image_desc_t *im;
3921 int gdi;
3923     graph_desc_t        *src,*dst;
3924     rrd_value_t                *data;
3925     long                step,steps;
3927     dst = &im->gdes[gdi];
3928     src = &im->gdes[dst->vidx];
3929     data = src->data + src->ds;
3930     steps = (src->end - src->start) / src->step;
3931 #if 0
3932 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3933     ,src->start
3934     ,src->end
3935     ,steps
3936     );
3937 #endif
3939     switch (dst->vf.op) {
3940         case VDEF_PERCENT: {
3941                 rrd_value_t *        array;
3942                 int                field;
3945                 if ((array = malloc(steps*sizeof(double)))==NULL) {
3946                     rrd_set_error("malloc VDEV_PERCENT");
3947                     return -1;
3948                 }
3949                 for (step=0;step < steps; step++) {
3950                     array[step]=data[step*src->ds_cnt];
3951                 }
3952                 qsort(array,step,sizeof(double),vdef_percent_compar);
3954                 field = (steps-1)*dst->vf.param/100;
3955                 dst->vf.val  = array[field];
3956                 dst->vf.when = 0;        /* no time component */
3957                 free(array);
3958 #if 0
3959 for(step=0;step<steps;step++)
3960 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3961 #endif
3962             }
3963             break;
3964         case VDEF_MAXIMUM:
3965             step=0;
3966             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3967             if (step == steps) {
3968                 dst->vf.val  = DNAN;
3969                 dst->vf.when = 0;
3970             } else {
3971                 dst->vf.val  = data[step*src->ds_cnt];
3972                 dst->vf.when = src->start + (step+1)*src->step;
3973             }
3974             while (step != steps) {
3975                 if (finite(data[step*src->ds_cnt])) {
3976                     if (data[step*src->ds_cnt] > dst->vf.val) {
3977                         dst->vf.val  = data[step*src->ds_cnt];
3978                         dst->vf.when = src->start + (step+1)*src->step;
3979                     }
3980                 }
3981                 step++;
3982             }
3983             break;
3984         case VDEF_TOTAL:
3985         case VDEF_AVERAGE: {
3986             int cnt=0;
3987             double sum=0.0;
3988             for (step=0;step<steps;step++) {
3989                 if (finite(data[step*src->ds_cnt])) {
3990                     sum += data[step*src->ds_cnt];
3991                     cnt ++;
3992                 };
3993             }
3994             if (cnt) {
3995                 if (dst->vf.op == VDEF_TOTAL) {
3996                     dst->vf.val  = sum*src->step;
3997                     dst->vf.when = 0;        /* no time component */
3998                 } else {
3999                     dst->vf.val = sum/cnt;
4000                     dst->vf.when = 0;        /* no time component */
4001                 };
4002             } else {
4003                 dst->vf.val  = DNAN;
4004                 dst->vf.when = 0;
4005             }
4006             }
4007             break;
4008         case VDEF_MINIMUM:
4009             step=0;
4010             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
4011             if (step == steps) {
4012                 dst->vf.val  = DNAN;
4013                 dst->vf.when = 0;
4014             } else {
4015                 dst->vf.val  = data[step*src->ds_cnt];
4016                 dst->vf.when = src->start + (step+1)*src->step;
4017             }
4018             while (step != steps) {
4019                 if (finite(data[step*src->ds_cnt])) {
4020                     if (data[step*src->ds_cnt] < dst->vf.val) {
4021                         dst->vf.val  = data[step*src->ds_cnt];
4022                         dst->vf.when = src->start + (step+1)*src->step;
4023                     }
4024                 }
4025                 step++;
4026             }
4027             break;
4028         case VDEF_FIRST:
4029             /* The time value returned here is one step before the
4030              * actual time value.  This is the start of the first
4031              * non-NaN interval.
4032              */
4033             step=0;
4034             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
4035             if (step == steps) { /* all entries were NaN */
4036                 dst->vf.val  = DNAN;
4037                 dst->vf.when = 0;
4038             } else {
4039                 dst->vf.val  = data[step*src->ds_cnt];
4040                 dst->vf.when = src->start + step*src->step;
4041             }
4042             break;
4043         case VDEF_LAST:
4044             /* The time value returned here is the
4045              * actual time value.  This is the end of the last
4046              * non-NaN interval.
4047              */
4048             step=steps-1;
4049             while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
4050             if (step < 0) { /* all entries were NaN */
4051                 dst->vf.val  = DNAN;
4052                 dst->vf.when = 0;
4053             } else {
4054                 dst->vf.val  = data[step*src->ds_cnt];
4055                 dst->vf.when = src->start + (step+1)*src->step;
4056             }
4057             break;
4058         case VDEF_LSLSLOPE:
4059         case VDEF_LSLINT:
4060         case VDEF_LSLCORREL:{
4061             /* Bestfit line by linear least squares method */ 
4063             int cnt=0;
4064             double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
4065             SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
4067             for (step=0;step<steps;step++) {
4068                 if (finite(data[step*src->ds_cnt])) {
4069                     cnt++;
4070                     SUMx  += step;
4071                     SUMxx += step * step;
4072                     SUMxy += step * data[step*src->ds_cnt];
4073                     SUMy  += data[step*src->ds_cnt];
4074                     SUMyy  += data[step*src->ds_cnt]*data[step*src->ds_cnt];
4075                 };
4076             }
4078             slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
4079             y_intercept = ( SUMy - slope*SUMx ) / cnt;
4080             correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
4082             if (cnt) {
4083                     if (dst->vf.op == VDEF_LSLSLOPE) {
4084                         dst->vf.val  = slope;
4085                         dst->vf.when = 0;
4086                     } else if (dst->vf.op == VDEF_LSLINT)  {
4087                         dst->vf.val = y_intercept;
4088                         dst->vf.when = 0;
4089                     } else if (dst->vf.op == VDEF_LSLCORREL)  {
4090                         dst->vf.val = correl;
4091                         dst->vf.when = 0;
4092                     };
4093                 
4094             } else {
4095                 dst->vf.val  = DNAN;
4096                 dst->vf.when = 0;
4097             }
4098         }
4099         break;
4100     }
4101     return 0;
4104 /* NaN < -INF < finite_values < INF */
4105 int
4106 vdef_percent_compar(a,b)
4107 const void *a,*b;
4109     /* Equality is not returned; this doesn't hurt except
4110      * (maybe) for a little performance.
4111      */
4113     /* First catch NaN values. They are smallest */
4114     if (isnan( *(double *)a )) return -1;
4115     if (isnan( *(double *)b )) return  1;
4117     /* NaN doesn't reach this part so INF and -INF are extremes.
4118      * The sign from isinf() is compatible with the sign we return
4119      */
4120     if (isinf( *(double *)a )) return isinf( *(double *)a );
4121     if (isinf( *(double *)b )) return isinf( *(double *)b );
4123     /* If we reach this, both values must be finite */
4124     if ( *(double *)a < *(double *)b ) return -1; else return 1;