Updated MS Visual C++ files from 7.0 to 7.1.
[rrdtool.git] / src / rrd_graph.c
1 /****************************************************************************
2  * RRDtool 1.1.x  Copyright Tobias Oetiker, 1997 - 2002
3  ****************************************************************************
4  * rrd__graph.c  make creates ne rrds
5  ****************************************************************************/
6
7
8 #include <sys/stat.h>
9
10 #include "rrd_tool.h"
11
12 #ifdef WIN32
13 #include <io.h>
14 #include <fcntl.h>
15 #endif
16
17 #ifdef HAVE_TIME_H
18 #include <time.h>
19 #endif
20
21 #ifdef HAVE_LOCALE_H
22 #include <locale.h>
23 #endif
24
25 #include "rrd_graph.h"
26
27 /* some constant definitions */
28
29
30 #ifdef WIN32
31 char rrd_win_default_font[80];
32 #endif
33
34 #ifndef RRD_DEFAULT_FONT
35 #ifndef WIN32
36 #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf" 
37 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
38 #endif
39 #endif
40
41 text_prop_t text_prop[] = {   
42      { 10.0, RRD_DEFAULT_FONT }, /* default */
43      { 12.0, RRD_DEFAULT_FONT }, /* title */
44      { 8.0,  RRD_DEFAULT_FONT },  /* axis */
45      { 10.0, RRD_DEFAULT_FONT },  /* unit */
46      { 10.0, RRD_DEFAULT_FONT }  /* legend */
47 };
48
49 xlab_t xlab[] = {
50     {0,        TMT_SECOND,30, TMT_MINUTE,5,  TMT_MINUTE,5,         0,"%H:%M"},
51     {2,        TMT_MINUTE,1,  TMT_MINUTE,5,  TMT_MINUTE,5,         0,"%H:%M"},
52     {5,        TMT_MINUTE,2,  TMT_MINUTE,10, TMT_MINUTE,10,        0,"%H:%M"},
53     {10,       TMT_MINUTE,5,  TMT_MINUTE,20, TMT_MINUTE,20,        0,"%H:%M"},
54     {30,       TMT_MINUTE,10, TMT_HOUR,1,    TMT_HOUR,1,           0,"%H:%M"},
55     {60,       TMT_MINUTE,30, TMT_HOUR,2,    TMT_HOUR,2,           0,"%H:%M"},
56     {180,      TMT_HOUR,1,    TMT_HOUR,6,    TMT_HOUR,6,           0,"%H:%M"},
57     /*{300,      TMT_HOUR,3,    TMT_HOUR,12,   TMT_HOUR,12,    12*3600,"%a %p"},  this looks silly*/
58     {600,      TMT_HOUR,6,    TMT_DAY,1,     TMT_DAY,1,      24*3600,"%a"},
59     {1800,     TMT_HOUR,12,   TMT_DAY,1,     TMT_DAY,2,      24*3600,"%a"},
60     {3600,     TMT_DAY,1,     TMT_WEEK,1,     TMT_WEEK,1,    7*24*3600,"Week %V"},
61     {3*3600,   TMT_WEEK,1,      TMT_MONTH,1,     TMT_WEEK,2,    7*24*3600,"Week %V"},
62     {6*3600,   TMT_MONTH,1,   TMT_MONTH,1,   TMT_MONTH,1, 30*24*3600,"%b"},
63     {48*3600,  TMT_MONTH,1,   TMT_MONTH,3,   TMT_MONTH,3, 30*24*3600,"%b"},
64     {10*24*3600, TMT_YEAR,1,  TMT_YEAR,1,    TMT_YEAR,1, 365*24*3600,"%y"},
65     {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
66 };
67
68 /* sensible logarithmic y label intervals ...
69    the first element of each row defines the possible starting points on the
70    y axis ... the other specify the */
71
72 double yloglab[][12]= {{ 1e9, 1,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0 },
73                        {  1e3, 1,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0 },
74                        {  1e1, 1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
75                        /* {  1e1, 1,  5,  0,  0,  0,  0,  0,  0,  0,  0,  0 }, */
76                        {  1e1, 1,  2.5,  5,  7.5,  0,  0,  0,  0,  0,  0,  0 },
77                        {  1e1, 1,  2,  4,  6,  8,  0,  0,  0,  0,  0,  0 },
78                        {  1e1, 1,  2,  3,  4,  5,  6,  7,  8,  9,  0,  0 },
79                        {  0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 }};
80
81 /* sensible y label intervals ...*/
82
83 ylab_t ylab[]= {
84     {0.1, {1,2, 5,10}},
85     {0.2, {1,5,10,20}},
86     {0.5, {1,2, 4,10}},
87     {1.0,   {1,2, 5,10}},
88     {2.0,   {1,5,10,20}},
89     {5.0,   {1,2, 4,10}},
90     {10.0,  {1,2, 5,10}},
91     {20.0,  {1,5,10,20}},
92     {50.0,  {1,2, 4,10}},
93     {100.0, {1,2, 5,10}},
94     {200.0, {1,5,10,20}},
95     {500.0, {1,2, 4,10}},
96     {0.0,   {0,0,0,0}}};
97
98
99 gfx_color_t graph_col[] =   /* default colors */
100 {    0xFFFFFFFF,   /* canvas     */
101      0xF0F0F0FF,   /* background */
102      0xD0D0D0FF,   /* shade A    */
103      0xA0A0A0FF,   /* shade B    */
104      0x909090FF,   /* grid       */
105      0xE05050FF,   /* major grid */
106      0x000000FF,   /* font       */ 
107      0x000000FF,   /* frame      */
108      0xFF0000FF  /* arrow      */
109 };
110
111
112 /* #define DEBUG */
113
114 #ifdef DEBUG
115 # define DPRINT(x)    (void)(printf x, printf("\n"))
116 #else
117 # define DPRINT(x)
118 #endif
119
120
121 /* initialize with xtr(im,0); */
122 int
123 xtr(image_desc_t *im,time_t mytime){
124     static double pixie;
125     if (mytime==0){
126         pixie = (double) im->xsize / (double)(im->end - im->start);
127         return im->xorigin;
128     }
129     return (int)((double)im->xorigin 
130                  + pixie * ( mytime - im->start ) );
131 }
132
133 /* translate data values into y coordinates */
134 double
135 ytr(image_desc_t *im, double value){
136     static double pixie;
137     double yval;
138     if (isnan(value)){
139       if(!im->logarithmic)
140         pixie = (double) im->ysize / (im->maxval - im->minval);
141       else 
142         pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
143       yval = im->yorigin;
144     } else if(!im->logarithmic) {
145       yval = im->yorigin - pixie * (value - im->minval);
146     } else {
147       if (value < im->minval) {
148         yval = im->yorigin;
149       } else {
150         yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
151       }
152     }
153     /* make sure we don't return anything too unreasonable. GD lib can
154        get terribly slow when drawing lines outside its scope. This is 
155        especially problematic in connection with the rigid option */
156     if (! im->rigid) {
157       /* keep yval as-is */
158     } else if (yval > im->yorigin) {
159       yval = im->yorigin+2;
160     } else if (yval < im->yorigin - im->ysize){
161       yval = im->yorigin - im->ysize - 2;
162     } 
163     return yval;
164 }
165
166
167
168 /* conversion function for symbolic entry names */
169
170
171 #define conv_if(VV,VVV) \
172    if (strcmp(#VV, string) == 0) return VVV ;
173
174 enum gf_en gf_conv(char *string){
175     
176     conv_if(PRINT,GF_PRINT)
177     conv_if(GPRINT,GF_GPRINT)
178     conv_if(COMMENT,GF_COMMENT)
179     conv_if(HRULE,GF_HRULE)
180     conv_if(VRULE,GF_VRULE)
181     conv_if(LINE,GF_LINE)
182     conv_if(AREA,GF_AREA)
183     conv_if(STACK,GF_STACK)
184     conv_if(TICK,GF_TICK)
185     conv_if(DEF,GF_DEF)
186     conv_if(CDEF,GF_CDEF)
187     conv_if(VDEF,GF_VDEF)
188     conv_if(PART,GF_PART)
189     conv_if(XPORT,GF_XPORT)
190     
191     return (-1);
192 }
193
194 enum gfx_if_en if_conv(char *string){
195     
196     conv_if(PNG,IF_PNG)
197     conv_if(SVG,IF_SVG)
198     conv_if(EPS,IF_EPS)
199     conv_if(PDF,IF_PDF)
200
201     return (-1);
202 }
203
204 enum tmt_en tmt_conv(char *string){
205
206     conv_if(SECOND,TMT_SECOND)
207     conv_if(MINUTE,TMT_MINUTE)
208     conv_if(HOUR,TMT_HOUR)
209     conv_if(DAY,TMT_DAY)
210     conv_if(WEEK,TMT_WEEK)
211     conv_if(MONTH,TMT_MONTH)
212     conv_if(YEAR,TMT_YEAR)
213     return (-1);
214 }
215
216 enum grc_en grc_conv(char *string){
217
218     conv_if(BACK,GRC_BACK)
219     conv_if(CANVAS,GRC_CANVAS)
220     conv_if(SHADEA,GRC_SHADEA)
221     conv_if(SHADEB,GRC_SHADEB)
222     conv_if(GRID,GRC_GRID)
223     conv_if(MGRID,GRC_MGRID)
224     conv_if(FONT,GRC_FONT)
225     conv_if(FRAME,GRC_FRAME)
226     conv_if(ARROW,GRC_ARROW)
227
228     return -1;  
229 }
230
231 enum text_prop_en text_prop_conv(char *string){
232       
233     conv_if(DEFAULT,TEXT_PROP_DEFAULT)
234     conv_if(TITLE,TEXT_PROP_TITLE)
235     conv_if(AXIS,TEXT_PROP_AXIS)
236     conv_if(UNIT,TEXT_PROP_UNIT)
237     conv_if(LEGEND,TEXT_PROP_LEGEND)
238     return -1;
239 }
240
241
242 #undef conv_if
243
244 int
245 im_free(image_desc_t *im)
246 {
247     unsigned long       i,ii;
248
249     if (im == NULL) return 0;
250     for(i=0;i<(unsigned)im->gdes_c;i++){
251       if (im->gdes[i].data_first){
252         /* careful here, because a single pointer can occur several times */
253           free (im->gdes[i].data);
254           if (im->gdes[i].ds_namv){
255               for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
256                   free(im->gdes[i].ds_namv[ii]);
257               free(im->gdes[i].ds_namv);
258           }
259       }
260       free (im->gdes[i].p_data);
261       free (im->gdes[i].rpnp);
262     }
263     free(im->gdes);
264     gfx_destroy(im->canvas);
265     return 0;
266 }
267
268 /* find SI magnitude symbol for the given number*/
269 void
270 auto_scale(
271            image_desc_t *im,   /* image description */
272            double *value,
273            char **symb_ptr,
274            double *magfact
275            )
276 {
277         
278     char *symbol[] = {"a", /* 10e-18 Atto */
279                       "f", /* 10e-15 Femto */
280                       "p", /* 10e-12 Pico */
281                       "n", /* 10e-9  Nano */
282                       "u", /* 10e-6  Micro */
283                       "m", /* 10e-3  Milli */
284                       " ", /* Base */
285                       "k", /* 10e3   Kilo */
286                       "M", /* 10e6   Mega */
287                       "G", /* 10e9   Giga */
288                       "T", /* 10e12  Tera */
289                       "P", /* 10e15  Peta */
290                       "E"};/* 10e18  Exa */
291
292     int symbcenter = 6;
293     int sindex;  
294
295     if (*value == 0.0 || isnan(*value) ) {
296         sindex = 0;
297         *magfact = 1.0;
298     } else {
299         sindex = floor(log(fabs(*value))/log((double)im->base)); 
300         *magfact = pow((double)im->base, (double)sindex);
301         (*value) /= (*magfact);
302     }
303     if ( sindex <= symbcenter && sindex >= -symbcenter) {
304         (*symb_ptr) = symbol[sindex+symbcenter];
305     }
306     else {
307         (*symb_ptr) = "?";
308     }
309 }
310
311
312 /* find SI magnitude symbol for the numbers on the y-axis*/
313 void 
314 si_unit(
315     image_desc_t *im   /* image description */
316 )
317 {
318
319     char symbol[] = {'a', /* 10e-18 Atto */ 
320                      'f', /* 10e-15 Femto */
321                      'p', /* 10e-12 Pico */
322                      'n', /* 10e-9  Nano */
323                      'u', /* 10e-6  Micro */
324                      'm', /* 10e-3  Milli */
325                      ' ', /* Base */
326                      'k', /* 10e3   Kilo */
327                      'M', /* 10e6   Mega */
328                      'G', /* 10e9   Giga */
329                      'T', /* 10e12  Tera */
330                      'P', /* 10e15  Peta */
331                      'E'};/* 10e18  Exa */
332
333     int   symbcenter = 6;
334     double digits;  
335     
336     if (im->unitsexponent != 9999) {
337         /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
338         digits = floor(im->unitsexponent / 3);
339     } else {
340         digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base)); 
341     }
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
347
348     if ( ((digits+symbcenter) < sizeof(symbol)) &&
349                     ((digits+symbcenter) >= 0) )
350         im->symbol = symbol[(int)digits+symbcenter];
351     else
352         im->symbol = ' ';
353  }
354
355 /*  move min and max values around to become sensible */
356
357 void 
358 expand_range(image_desc_t *im)
359 {
360     double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
361                               600.0,500.0,400.0,300.0,250.0,
362                               200.0,125.0,100.0,90.0,80.0,
363                               75.0,70.0,60.0,50.0,40.0,30.0,
364                               25.0,20.0,10.0,9.0,8.0,
365                               7.0,6.0,5.0,4.0,3.5,3.0,
366                               2.5,2.0,1.8,1.5,1.2,1.0,
367                               0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
368     
369     double scaled_min,scaled_max;  
370     double adj;
371     int i;
372     
373
374     
375 #ifdef DEBUG
376     printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
377            im->minval,im->maxval,im->magfact);
378 #endif
379
380     if (isnan(im->ygridstep)){
381         if(im->extra_flags & ALTAUTOSCALE) {
382             /* measure the amplitude of the function. Make sure that
383                graph boundaries are slightly higher then max/min vals
384                so we can see amplitude on the graph */
385               double delt, fact;
386
387               delt = im->maxval - im->minval;
388               adj = delt * 0.1;
389               fact = 2.0 * pow(10.0,
390                     floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
391               if (delt < fact) {
392                 adj = (fact - delt) * 0.55;
393 #ifdef DEBUG
394               printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
395 #endif
396               }
397               im->minval -= adj;
398               im->maxval += adj;
399         }
400         else if(im->extra_flags & ALTAUTOSCALE_MAX) {
401             /* measure the amplitude of the function. Make sure that
402                graph boundaries are slightly higher than max vals
403                so we can see amplitude on the graph */
404               adj = (im->maxval - im->minval) * 0.1;
405               im->maxval += adj;
406         }
407         else {
408             scaled_min = im->minval / im->magfact;
409             scaled_max = im->maxval / im->magfact;
410             
411             for (i=1; sensiblevalues[i] > 0; i++){
412                 if (sensiblevalues[i-1]>=scaled_min &&
413                     sensiblevalues[i]<=scaled_min)      
414                     im->minval = sensiblevalues[i]*(im->magfact);
415                 
416                 if (-sensiblevalues[i-1]<=scaled_min &&
417                 -sensiblevalues[i]>=scaled_min)
418                     im->minval = -sensiblevalues[i-1]*(im->magfact);
419                 
420                 if (sensiblevalues[i-1] >= scaled_max &&
421                     sensiblevalues[i] <= scaled_max)
422                     im->maxval = sensiblevalues[i-1]*(im->magfact);
423                 
424                 if (-sensiblevalues[i-1]<=scaled_max &&
425                     -sensiblevalues[i] >=scaled_max)
426                     im->maxval = -sensiblevalues[i]*(im->magfact);
427             }
428         }
429     } else {
430         /* adjust min and max to the grid definition if there is one */
431         im->minval = (double)im->ylabfact * im->ygridstep * 
432             floor(im->minval / ((double)im->ylabfact * im->ygridstep));
433         im->maxval = (double)im->ylabfact * im->ygridstep * 
434             ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
435     }
436     
437 #ifdef DEBUG
438     fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
439            im->minval,im->maxval,im->magfact);
440 #endif
441 }
442
443 void
444 apply_gridfit(image_desc_t *im)
445 {
446   if (isnan(im->minval) || isnan(im->maxval))
447     return;
448   ytr(im,DNAN);
449   if (im->logarithmic) {
450     double ya, yb, ypix, ypixfrac;
451     double log10_range = log10(im->maxval) - log10(im->minval);
452     ya = pow((double)10, floor(log10(im->minval)));
453     while (ya < im->minval)
454       ya *= 10;
455     if (ya > im->maxval)
456       return; /* don't have y=10^x gridline */
457     yb = ya * 10;
458     if (yb <= im->maxval) {
459       /* we have at least 2 y=10^x gridlines.
460          Make sure distance between them in pixels
461          are an integer by expanding im->maxval */
462       double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
463       double factor = y_pixel_delta / floor(y_pixel_delta);
464       double new_log10_range = factor * log10_range;
465       double new_ymax_log10 = log10(im->minval) + new_log10_range;
466       im->maxval = pow(10, new_ymax_log10);
467       ytr(im, DNAN); /* reset precalc */
468       log10_range = log10(im->maxval) - log10(im->minval);
469     }
470     /* make sure first y=10^x gridline is located on 
471        integer pixel position by moving scale slightly 
472        downwards (sub-pixel movement) */
473     ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
474     ypixfrac = ypix - floor(ypix);
475     if (ypixfrac > 0 && ypixfrac < 1) {
476       double yfrac = ypixfrac / im->ysize;
477       im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
478       im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
479       ytr(im, DNAN); /* reset precalc */
480     }
481   } else {
482     /* Make sure we have an integer pixel distance between
483        each minor gridline */
484     double ypos1 = ytr(im, im->minval);
485     double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
486     double y_pixel_delta = ypos1 - ypos2;
487     double factor = y_pixel_delta / floor(y_pixel_delta);
488     double new_range = factor * (im->maxval - im->minval);
489     double gridstep = im->ygrid_scale.gridstep;
490     double minor_y, minor_y_px, minor_y_px_frac;
491     im->maxval = im->minval + new_range;
492     ytr(im, DNAN); /* reset precalc */
493     /* make sure first minor gridline is on integer pixel y coord */
494     minor_y = gridstep * floor(im->minval / gridstep);
495     while (minor_y < im->minval)
496       minor_y += gridstep;
497     minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
498     minor_y_px_frac = minor_y_px - floor(minor_y_px);
499     if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
500       double yfrac = minor_y_px_frac / im->ysize;
501       double range = im->maxval - im->minval;
502       im->minval = im->minval - yfrac * range;
503       im->maxval = im->maxval - yfrac * range;
504       ytr(im, DNAN); /* reset precalc */
505     }
506     calc_horizontal_grid(im); /* recalc with changed im->maxval */
507   }
508 }
509
510 /* reduce data reimplementation by Alex */
511
512 void
513 reduce_data(
514     enum cf_en     cf,         /* which consolidation function ?*/
515     unsigned long  cur_step,   /* step the data currently is in */
516     time_t         *start,     /* start, end and step as requested ... */
517     time_t         *end,       /* ... by the application will be   ... */
518     unsigned long  *step,      /* ... adjusted to represent reality    */
519     unsigned long  *ds_cnt,    /* number of data sources in file */
520     rrd_value_t    **data)     /* two dimensional array containing the data */
521 {
522     int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
523     unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
524     rrd_value_t    *srcptr,*dstptr;
525
526     (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
527     dstptr = *data;
528     srcptr = *data;
529     row_cnt = ((*end)-(*start))/cur_step;
530
531 #ifdef DEBUG
532 #define DEBUG_REDUCE
533 #endif
534 #ifdef DEBUG_REDUCE
535 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
536                         row_cnt,reduce_factor,*start,*end,cur_step);
537 for (col=0;col<row_cnt;col++) {
538     printf("time %10lu: ",*start+(col+1)*cur_step);
539     for (i=0;i<*ds_cnt;i++)
540         printf(" %8.2e",srcptr[*ds_cnt*col+i]);
541     printf("\n");
542 }
543 #endif
544
545     /* We have to combine [reduce_factor] rows of the source
546     ** into one row for the destination.  Doing this we also
547     ** need to take care to combine the correct rows.  First
548     ** alter the start and end time so that they are multiples
549     ** of the new step time.  We cannot reduce the amount of
550     ** time so we have to move the end towards the future and
551     ** the start towards the past.
552     */
553     end_offset = (*end) % (*step);
554     start_offset = (*start) % (*step);
555
556     /* If there is a start offset (which cannot be more than
557     ** one destination row), skip the appropriate number of
558     ** source rows and one destination row.  The appropriate
559     ** number is what we do know (start_offset/cur_step) of
560     ** the new interval (*step/cur_step aka reduce_factor).
561     */
562 #ifdef DEBUG_REDUCE
563 printf("start_offset: %lu  end_offset: %lu\n",start_offset,end_offset);
564 printf("row_cnt before:  %lu\n",row_cnt);
565 #endif
566     if (start_offset) {
567         (*start) = (*start)-start_offset;
568         skiprows=reduce_factor-start_offset/cur_step;
569         srcptr+=skiprows* *ds_cnt;
570         for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
571         row_cnt-=skiprows;
572     }
573 #ifdef DEBUG_REDUCE
574 printf("row_cnt between: %lu\n",row_cnt);
575 #endif
576
577     /* At the end we have some rows that are not going to be
578     ** used, the amount is end_offset/cur_step
579     */
580     if (end_offset) {
581         (*end) = (*end)-end_offset+(*step);
582         skiprows = end_offset/cur_step;
583         row_cnt-=skiprows;
584     }
585 #ifdef DEBUG_REDUCE
586 printf("row_cnt after:   %lu\n",row_cnt);
587 #endif
588
589 /* Sanity check: row_cnt should be multiple of reduce_factor */
590 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
591
592     if (row_cnt%reduce_factor) {
593         printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
594                                 row_cnt,reduce_factor);
595         printf("BUG in reduce_data()\n");
596         exit(1);
597     }
598
599     /* Now combine reduce_factor intervals at a time
600     ** into one interval for the destination.
601     */
602
603     for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
604         for (col=0;col<(*ds_cnt);col++) {
605             rrd_value_t newval=DNAN;
606             unsigned long validval=0;
607
608             for (i=0;i<reduce_factor;i++) {
609                 if (isnan(srcptr[i*(*ds_cnt)+col])) {
610                     continue;
611                 }
612                 validval++;
613                 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
614                 else {
615                     switch (cf) {
616                         case CF_HWPREDICT:
617                         case CF_DEVSEASONAL:
618                         case CF_DEVPREDICT:
619                         case CF_SEASONAL:
620                         case CF_AVERAGE:
621                             newval += srcptr[i*(*ds_cnt)+col];
622                             break;
623                         case CF_MINIMUM:
624                             newval = min (newval,srcptr[i*(*ds_cnt)+col]);
625                             break;
626                         case CF_FAILURES: 
627                         /* an interval contains a failure if any subintervals contained a failure */
628                         case CF_MAXIMUM:
629                             newval = max (newval,srcptr[i*(*ds_cnt)+col]);
630                             break;
631                         case CF_LAST:
632                             newval = srcptr[i*(*ds_cnt)+col];
633                             break;
634                     }
635                 }
636             }
637             if (validval == 0){newval = DNAN;} else{
638                 switch (cf) {
639                     case CF_HWPREDICT:
640             case CF_DEVSEASONAL:
641                     case CF_DEVPREDICT:
642                     case CF_SEASONAL:
643                     case CF_AVERAGE:                
644                        newval /= validval;
645                         break;
646                     case CF_MINIMUM:
647                     case CF_FAILURES:
648                     case CF_MAXIMUM:
649                     case CF_LAST:
650                         break;
651                 }
652             }
653             *dstptr++=newval;
654         }
655         srcptr+=(*ds_cnt)*reduce_factor;
656         row_cnt-=reduce_factor;
657     }
658     /* If we had to alter the endtime, we didn't have enough
659     ** source rows to fill the last row. Fill it with NaN.
660     */
661     if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
662 #ifdef DEBUG_REDUCE
663     row_cnt = ((*end)-(*start))/ *step;
664     srcptr = *data;
665     printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
666                                 row_cnt,*start,*end,*step);
667 for (col=0;col<row_cnt;col++) {
668     printf("time %10lu: ",*start+(col+1)*(*step));
669     for (i=0;i<*ds_cnt;i++)
670         printf(" %8.2e",srcptr[*ds_cnt*col+i]);
671     printf("\n");
672 }
673 #endif
674 }
675
676
677 /* get the data required for the graphs from the 
678    relevant rrds ... */
679
680 int
681 data_fetch(image_desc_t *im )
682 {
683     unsigned int i,ii;
684     int         skip;
685
686     /* pull the data from the log files ... */
687     for (i=0;i<im->gdes_c;i++){
688         /* only GF_DEF elements fetch data */
689         if (im->gdes[i].gf != GF_DEF) 
690             continue;
691
692         skip=0;
693         /* do we have it already ?*/
694         for (ii=0;ii<i;ii++) {
695             if (im->gdes[ii].gf != GF_DEF) 
696                 continue;
697             if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
698                         && (im->gdes[i].cf    == im->gdes[ii].cf)
699                         && (im->gdes[i].start == im->gdes[ii].start)
700                         && (im->gdes[i].end   == im->gdes[ii].end)
701                         && (im->gdes[i].step  == im->gdes[ii].step)) {
702                 /* OK, the data is already there.
703                 ** Just copy the header portion
704                 */
705                 im->gdes[i].start = im->gdes[ii].start;
706                 im->gdes[i].end = im->gdes[ii].end;
707                 im->gdes[i].step = im->gdes[ii].step;
708                 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
709                 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;             
710                 im->gdes[i].data = im->gdes[ii].data;
711                 im->gdes[i].data_first = 0;
712                 skip=1;
713             }
714             if (skip) 
715                 break;
716         }
717         if (! skip) {
718             unsigned long  ft_step = im->gdes[i].step ;
719             
720             if((rrd_fetch_fn(im->gdes[i].rrd,
721                              im->gdes[i].cf,
722                              &im->gdes[i].start,
723                              &im->gdes[i].end,
724                              &ft_step,
725                              &im->gdes[i].ds_cnt,
726                              &im->gdes[i].ds_namv,
727                              &im->gdes[i].data)) == -1){                
728                 return -1;
729             }
730             im->gdes[i].data_first = 1;     
731         
732             if (ft_step < im->gdes[i].step) {
733                 reduce_data(im->gdes[i].cf,
734                             ft_step,
735                             &im->gdes[i].start,
736                             &im->gdes[i].end,
737                             &im->gdes[i].step,
738                             &im->gdes[i].ds_cnt,
739                             &im->gdes[i].data);
740             } else {
741                 im->gdes[i].step = ft_step;
742             }
743         }
744         
745         /* lets see if the required data source is realy there */
746         for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
747             if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
748                 im->gdes[i].ds=ii; }
749         }
750         if (im->gdes[i].ds== -1){
751             rrd_set_error("No DS called '%s' in '%s'",
752                           im->gdes[i].ds_nam,im->gdes[i].rrd);
753             return -1; 
754         }
755         
756     }
757     return 0;
758 }
759
760 /* evaluate the expressions in the CDEF functions */
761
762 /*************************************************************
763  * CDEF stuff 
764  *************************************************************/
765
766 long
767 find_var_wrapper(void *arg1, char *key)
768 {
769    return find_var((image_desc_t *) arg1, key);
770 }
771
772 /* find gdes containing var*/
773 long
774 find_var(image_desc_t *im, char *key){
775     long ii;
776     for(ii=0;ii<im->gdes_c-1;ii++){
777         if((im->gdes[ii].gf == GF_DEF 
778             || im->gdes[ii].gf == GF_VDEF
779             || im->gdes[ii].gf == GF_CDEF) 
780            && (strcmp(im->gdes[ii].vname,key) == 0)){
781             return ii; 
782         }          
783     }               
784     return -1;
785 }
786
787 /* find the largest common denominator for all the numbers
788    in the 0 terminated num array */
789 long
790 lcd(long *num){
791     long rest;
792     int i;
793     for (i=0;num[i+1]!=0;i++){
794         do { 
795             rest=num[i] % num[i+1];
796             num[i]=num[i+1]; num[i+1]=rest;
797         } while (rest!=0);
798         num[i+1] = num[i];
799     }
800 /*    return i==0?num[i]:num[i-1]; */
801       return num[i];
802 }
803
804 /* run the rpn calculator on all the VDEF and CDEF arguments */
805 int
806 data_calc( image_desc_t *im){
807
808     int       gdi;
809     int       dataidx;
810     long      *steparray, rpi;
811     int       stepcnt;
812     time_t    now;
813     rpnstack_t rpnstack;
814
815     rpnstack_init(&rpnstack);
816
817     for (gdi=0;gdi<im->gdes_c;gdi++){
818         /* Look for GF_VDEF and GF_CDEF in the same loop,
819          * so CDEFs can use VDEFs and vice versa
820          */
821         switch (im->gdes[gdi].gf) {
822             case GF_XPORT:
823               break;
824             case GF_VDEF:
825                 /* A VDEF has no DS.  This also signals other parts
826                  * of rrdtool that this is a VDEF value, not a CDEF.
827                  */
828                 im->gdes[gdi].ds_cnt = 0;
829                 if (vdef_calc(im,gdi)) {
830                     rrd_set_error("Error processing VDEF '%s'"
831                         ,im->gdes[gdi].vname
832                         );
833                     rpnstack_free(&rpnstack);
834                     return -1;
835                 }
836                 break;
837             case GF_CDEF:
838                 im->gdes[gdi].ds_cnt = 1;
839                 im->gdes[gdi].ds = 0;
840                 im->gdes[gdi].data_first = 1;
841                 im->gdes[gdi].start = 0;
842                 im->gdes[gdi].end = 0;
843                 steparray=NULL;
844                 stepcnt = 0;
845                 dataidx=-1;
846
847                 /* Find the variables in the expression.
848                  * - VDEF variables are substituted by their values
849                  *   and the opcode is changed into OP_NUMBER.
850                  * - CDEF variables are analized for their step size,
851                  *   the lowest common denominator of all the step
852                  *   sizes of the data sources involved is calculated
853                  *   and the resulting number is the step size for the
854                  *   resulting data source.
855                  */
856                 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
857                 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
858                    im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
859                         long ptr = im->gdes[gdi].rpnp[rpi].ptr;
860                         if (im->gdes[ptr].ds_cnt == 0) {
861 #if 0
862                         printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
863         im->gdes[gdi].vname,
864         im->gdes[ptr].vname);
865                         printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
866 #endif
867                             im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
868                             im->gdes[gdi].rpnp[rpi].op  = OP_NUMBER;
869                         } else {
870                         if ((steparray =
871                                  rrd_realloc(steparray,
872                                                          (++stepcnt+1)*sizeof(*steparray)))==NULL){
873                                 rrd_set_error("realloc steparray");
874                                 rpnstack_free(&rpnstack);
875                                 return -1;
876                             };
877
878                             steparray[stepcnt-1] = im->gdes[ptr].step;
879
880                             /* adjust start and end of cdef (gdi) so
881                              * that it runs from the latest start point
882                              * to the earliest endpoint of any of the
883                              * rras involved (ptr)
884                              */
885                             if(im->gdes[gdi].start < im->gdes[ptr].start)
886                                 im->gdes[gdi].start = im->gdes[ptr].start;
887
888                             if(im->gdes[gdi].end == 0 ||
889                                         im->gdes[gdi].end > im->gdes[ptr].end)
890                                 im->gdes[gdi].end = im->gdes[ptr].end;
891                 
892                             /* store pointer to the first element of
893                              * the rra providing data for variable,
894                              * further save step size and data source
895                              * count of this rra
896                              */ 
897                             im->gdes[gdi].rpnp[rpi].data =  im->gdes[ptr].data + im->gdes[ptr].ds;
898                             im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
899                             im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
900
901                             /* backoff the *.data ptr; this is done so
902                              * rpncalc() function doesn't have to treat
903                              * the first case differently
904                              */
905                         } /* if ds_cnt != 0 */
906                     } /* if OP_VARIABLE */
907                 } /* loop through all rpi */
908
909                 /* move the data pointers to the correct period */
910                 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
911                 if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
912                    im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER){
913                         long ptr = im->gdes[gdi].rpnp[rpi].ptr;
914                         if(im->gdes[gdi].start > im->gdes[ptr].start) {
915                             im->gdes[gdi].rpnp[rpi].data += im->gdes[gdi].rpnp[rpi].ds_cnt;
916                         }
917                      }
918                 }
919         
920
921                 if(steparray == NULL){
922                     rrd_set_error("rpn expressions without DEF"
923                                 " or CDEF variables are not supported");
924                     rpnstack_free(&rpnstack);
925                     return -1;    
926                 }
927                 steparray[stepcnt]=0;
928                 /* Now find the resulting step.  All steps in all
929                  * used RRAs have to be visited
930                  */
931                 im->gdes[gdi].step = lcd(steparray);
932                 free(steparray);
933                 if((im->gdes[gdi].data = malloc((
934                                 (im->gdes[gdi].end-im->gdes[gdi].start) 
935                                     / im->gdes[gdi].step)
936                                     * sizeof(double)))==NULL){
937                     rrd_set_error("malloc im->gdes[gdi].data");
938                     rpnstack_free(&rpnstack);
939                     return -1;
940                 }
941         
942                 /* Step through the new cdef results array and
943                  * calculate the values
944                  */
945                 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
946                                 now<=im->gdes[gdi].end;
947                                 now += im->gdes[gdi].step)
948                 {
949                     rpnp_t  *rpnp = im -> gdes[gdi].rpnp;
950
951                     /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
952                      * in this case we are advancing by timesteps;
953                      * we use the fact that time_t is a synonym for long
954                      */
955                     if (rpn_calc(rpnp,&rpnstack,(long) now, 
956                                 im->gdes[gdi].data,++dataidx) == -1) {
957                         /* rpn_calc sets the error string */
958                         rpnstack_free(&rpnstack); 
959                         return -1;
960                     } 
961                 } /* enumerate over time steps within a CDEF */
962                 break;
963             default:
964                 continue;
965         }
966     } /* enumerate over CDEFs */
967     rpnstack_free(&rpnstack);
968     return 0;
969 }
970
971 /* massage data so, that we get one value for each x coordinate in the graph */
972 int
973 data_proc( image_desc_t *im ){
974     long i,ii;
975     double pixstep = (double)(im->end-im->start)
976         /(double)im->xsize; /* how much time 
977                                passes in one pixel */
978     double paintval;
979     double minval=DNAN,maxval=DNAN;
980     
981     unsigned long gr_time;    
982
983     /* memory for the processed data */
984     for(i=0;i<im->gdes_c;i++) {
985         if((im->gdes[i].gf==GF_LINE) ||
986                 (im->gdes[i].gf==GF_AREA) ||
987                 (im->gdes[i].gf==GF_TICK) ||
988                 (im->gdes[i].gf==GF_STACK)) {
989             if((im->gdes[i].p_data = malloc((im->xsize +1)
990                                         * sizeof(rrd_value_t)))==NULL){
991                 rrd_set_error("malloc data_proc");
992                 return -1;
993             }
994         }
995     }
996
997     for (i=0;i<im->xsize;i++) { /* for each pixel */
998         long vidx;
999         gr_time = im->start+pixstep*i; /* time of the current step */
1000         paintval=0.0;
1001         
1002         for (ii=0;ii<im->gdes_c;ii++) {
1003             double value;
1004             switch (im->gdes[ii].gf) {
1005                 case GF_LINE:
1006                 case GF_AREA:
1007                 case GF_TICK:
1008                     if (!im->gdes[ii].stack)
1009                         paintval = 0.0;
1010                 case GF_STACK:
1011                     value = im->gdes[ii].yrule;
1012                     if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1013                         /* The time of the data doesn't necessarily match
1014                         ** the time of the graph. Beware.
1015                         */
1016                         vidx = im->gdes[ii].vidx;
1017                         if (    (gr_time >= im->gdes[vidx].start) &&
1018                                 (gr_time <= im->gdes[vidx].end) ) {
1019                             value = im->gdes[vidx].data[
1020                                 (unsigned long) floor(
1021                                     (double)(gr_time - im->gdes[vidx].start)
1022                                                 / im->gdes[vidx].step)
1023                                 * im->gdes[vidx].ds_cnt
1024                                 + im->gdes[vidx].ds
1025                             ];
1026                         } else {
1027                             value = DNAN;
1028                         }
1029                     };
1030
1031                     if (! isnan(value)) {
1032                         paintval += value;
1033                         im->gdes[ii].p_data[i] = paintval;
1034                         /* GF_TICK: the data values are not
1035                         ** relevant for min and max
1036                         */
1037                         if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1038                             if (isnan(minval) || paintval <  minval)
1039                                 minval = paintval;
1040                             if (isnan(maxval) || paintval >  maxval)
1041                                 maxval = paintval;
1042                         }
1043                     } else {
1044                         im->gdes[ii].p_data[i] = DNAN;
1045                     }
1046                     break;
1047                 default:
1048                     break;
1049             }
1050         }
1051     }
1052
1053     /* if min or max have not been asigned a value this is because
1054        there was no data in the graph ... this is not good ...
1055        lets set these to dummy values then ... */
1056
1057     if (isnan(minval)) minval = 0.0;
1058     if (isnan(maxval)) maxval = 1.0;
1059     
1060     /* adjust min and max values */
1061     if (isnan(im->minval) 
1062         /* don't adjust low-end with log scale */
1063         || ((!im->logarithmic && !im->rigid) && im->minval > minval)
1064         )
1065         im->minval = minval;
1066     if (isnan(im->maxval) 
1067         || (!im->rigid && im->maxval < maxval)
1068         ) {
1069         if (im->logarithmic)
1070             im->maxval = maxval * 1.1;
1071         else
1072             im->maxval = maxval;
1073     }
1074     /* make sure min and max are not equal */
1075     if (im->minval == im->maxval) {
1076         im->maxval *= 1.01; 
1077         if (! im->logarithmic) {
1078             im->minval *= 0.99;
1079         }
1080         /* make sure min and max are not both zero */
1081         if (im->maxval == 0.0) {
1082             im->maxval = 1.0;
1083         }
1084     }
1085     return 0;
1086 }
1087
1088
1089
1090 /* identify the point where the first gridline, label ... gets placed */
1091
1092 time_t
1093 find_first_time(
1094     time_t   start, /* what is the initial time */
1095     enum tmt_en baseint,  /* what is the basic interval */
1096     long     basestep /* how many if these do we jump a time */
1097     )
1098 {
1099     struct tm tm;
1100     localtime_r(&start, &tm);
1101     switch(baseint){
1102     case TMT_SECOND:
1103         tm.tm_sec -= tm.tm_sec % basestep; break;
1104     case TMT_MINUTE: 
1105         tm.tm_sec=0;
1106         tm.tm_min -= tm.tm_min % basestep; 
1107         break;
1108     case TMT_HOUR:
1109         tm.tm_sec=0;
1110         tm.tm_min = 0;
1111         tm.tm_hour -= tm.tm_hour % basestep; break;
1112     case TMT_DAY:
1113         /* we do NOT look at the basestep for this ... */
1114         tm.tm_sec=0;
1115         tm.tm_min = 0;
1116         tm.tm_hour = 0; break;
1117     case TMT_WEEK:
1118         /* we do NOT look at the basestep for this ... */
1119         tm.tm_sec=0;
1120         tm.tm_min = 0;
1121         tm.tm_hour = 0;
1122         tm.tm_mday -= tm.tm_wday -1;    /* -1 because we want the monday */
1123         if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1124         break;
1125     case TMT_MONTH:
1126         tm.tm_sec=0;
1127         tm.tm_min = 0;
1128         tm.tm_hour = 0;
1129         tm.tm_mday = 1;
1130         tm.tm_mon -= tm.tm_mon % basestep; break;
1131
1132     case TMT_YEAR:
1133         tm.tm_sec=0;
1134         tm.tm_min = 0;
1135         tm.tm_hour = 0;
1136         tm.tm_mday = 1;
1137         tm.tm_mon = 0;
1138         tm.tm_year -= (tm.tm_year+1900) % basestep;
1139         
1140     }
1141     return mktime(&tm);
1142 }
1143 /* identify the point where the next gridline, label ... gets placed */
1144 time_t 
1145 find_next_time(
1146     time_t   current, /* what is the initial time */
1147     enum tmt_en baseint,  /* what is the basic interval */
1148     long     basestep /* how many if these do we jump a time */
1149     )
1150 {
1151     struct tm tm;
1152     time_t madetime;
1153     localtime_r(&current, &tm);
1154     do {
1155         switch(baseint){
1156         case TMT_SECOND:
1157             tm.tm_sec += basestep; break;
1158         case TMT_MINUTE: 
1159             tm.tm_min += basestep; break;
1160         case TMT_HOUR:
1161             tm.tm_hour += basestep; break;
1162         case TMT_DAY:
1163             tm.tm_mday += basestep; break;
1164         case TMT_WEEK:
1165             tm.tm_mday += 7*basestep; break;
1166         case TMT_MONTH:
1167             tm.tm_mon += basestep; break;
1168         case TMT_YEAR:
1169             tm.tm_year += basestep;     
1170         }
1171         madetime = mktime(&tm);
1172     } while (madetime == -1); /* this is necessary to skip impssible times
1173                                  like the daylight saving time skips */
1174     return madetime;
1175           
1176 }
1177
1178
1179 /* calculate values required for PRINT and GPRINT functions */
1180
1181 int
1182 print_calc(image_desc_t *im, char ***prdata) 
1183 {
1184     long i,ii,validsteps;
1185     double printval;
1186     time_t printtime;
1187     int graphelement = 0;
1188     long vidx;
1189     int max_ii; 
1190     double magfact = -1;
1191     char *si_symb = "";
1192     char *percent_s;
1193     int prlines = 1;
1194     if (im->imginfo) prlines++;
1195     for(i=0;i<im->gdes_c;i++){
1196         switch(im->gdes[i].gf){
1197         case GF_PRINT:
1198             prlines++;
1199             if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1200                 rrd_set_error("realloc prdata");
1201                 return 0;
1202             }
1203         case GF_GPRINT:
1204             /* PRINT and GPRINT can now print VDEF generated values.
1205              * There's no need to do any calculations on them as these
1206              * calculations were already made.
1207              */
1208             vidx = im->gdes[i].vidx;
1209             if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1210                 printval = im->gdes[vidx].vf.val;
1211                 printtime = im->gdes[vidx].vf.when;
1212             } else { /* need to calculate max,min,avg etcetera */
1213                 max_ii =((im->gdes[vidx].end 
1214                         - im->gdes[vidx].start)
1215                         / im->gdes[vidx].step
1216                         * im->gdes[vidx].ds_cnt);
1217                 printval = DNAN;
1218                 validsteps = 0;
1219                 for(    ii=im->gdes[vidx].ds;
1220                         ii < max_ii;
1221                         ii+=im->gdes[vidx].ds_cnt){
1222                     if (! finite(im->gdes[vidx].data[ii]))
1223                         continue;
1224                     if (isnan(printval)){
1225                         printval = im->gdes[vidx].data[ii];
1226                         validsteps++;
1227                         continue;
1228                     }
1229
1230                     switch (im->gdes[i].cf){
1231                         case CF_HWPREDICT:
1232                         case CF_DEVPREDICT:
1233                         case CF_DEVSEASONAL:
1234                         case CF_SEASONAL:
1235                         case CF_AVERAGE:
1236                             validsteps++;
1237                             printval += im->gdes[vidx].data[ii];
1238                             break;
1239                         case CF_MINIMUM:
1240                             printval = min( printval, im->gdes[vidx].data[ii]);
1241                             break;
1242                         case CF_FAILURES:
1243                         case CF_MAXIMUM:
1244                             printval = max( printval, im->gdes[vidx].data[ii]);
1245                             break;
1246                         case CF_LAST:
1247                             printval = im->gdes[vidx].data[ii];
1248                     }
1249                 }
1250                 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1251                     if (validsteps > 1) {
1252                         printval = (printval / validsteps);
1253                     }
1254                 }
1255             } /* prepare printval */
1256
1257             if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1258                 char ctime_buf[128]; /* PS: for ctime_r, must be >= 26 chars */
1259                 if (im->gdes[i].gf == GF_PRINT){
1260                     (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1261                     sprintf((*prdata)[prlines-2],"%s (%lu)",
1262                                         ctime_r(&printtime,ctime_buf),printtime);
1263                     (*prdata)[prlines-1] = NULL;
1264                 } else {
1265                     sprintf(im->gdes[i].legend,"%s (%lu)",
1266                                         ctime_r(&printtime,ctime_buf),printtime);
1267                     graphelement = 1;
1268                 }
1269             } else {
1270             if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1271                 /* Magfact is set to -1 upon entry to print_calc.  If it
1272                  * is still less than 0, then we need to run auto_scale.
1273                  * Otherwise, put the value into the correct units.  If
1274                  * the value is 0, then do not set the symbol or magnification
1275                  * so next the calculation will be performed again. */
1276                 if (magfact < 0.0) {
1277                     auto_scale(im,&printval,&si_symb,&magfact);
1278                     if (printval == 0.0)
1279                         magfact = -1.0;
1280                 } else {
1281                     printval /= magfact;
1282                 }
1283                 *(++percent_s) = 's';
1284             } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1285                 auto_scale(im,&printval,&si_symb,&magfact);
1286             }
1287
1288             if (im->gdes[i].gf == GF_PRINT){
1289                 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1290                 (*prdata)[prlines-1] = NULL;
1291                 if (bad_format(im->gdes[i].format)) {
1292                         rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1293                         return -1;
1294                 }
1295 #ifdef HAVE_SNPRINTF
1296                 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1297 #else
1298                 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1299 #endif
1300             } else {
1301                 /* GF_GPRINT */
1302
1303                 if (bad_format(im->gdes[i].format)) {
1304                         rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1305                         return -1;
1306                 }
1307 #ifdef HAVE_SNPRINTF
1308                 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1309 #else
1310                 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1311 #endif
1312                 graphelement = 1;
1313             }
1314             }
1315             break;
1316         case GF_LINE:
1317         case GF_AREA:
1318         case GF_TICK:
1319         case GF_STACK:
1320         case GF_HRULE:
1321         case GF_VRULE:
1322             graphelement = 1;
1323             break;
1324         case GF_COMMENT:
1325         case GF_DEF:
1326         case GF_CDEF:       
1327         case GF_VDEF:       
1328         case GF_PART:
1329         case GF_XPORT:
1330             break;
1331         }
1332     }
1333     return graphelement;
1334 }
1335
1336
1337 /* place legends with color spots */
1338 int
1339 leg_place(image_desc_t *im)
1340 {
1341     /* graph labels */
1342     int   interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1343     int   box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1344     int   border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1345     int   fill=0, fill_last;
1346     int   leg_c = 0;
1347     int   leg_x = border, leg_y = im->yimg;
1348     int   leg_cc;
1349     int   glue = 0;
1350     int   i,ii, mark = 0;
1351     char  prt_fctn; /*special printfunctions */
1352     int  *legspace;
1353
1354   if( !(im->extra_flags & NOLEGEND) ) {
1355     if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1356        rrd_set_error("malloc for legspace");
1357        return -1;
1358     }
1359
1360     for(i=0;i<im->gdes_c;i++){
1361         fill_last = fill;
1362         
1363         /* hid legends for rules which are not displayed */
1364         
1365         if (im->gdes[i].gf == GF_HRULE &&
1366             (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1367             im->gdes[i].legend[0] = '\0';
1368
1369         if (im->gdes[i].gf == GF_VRULE &&
1370             (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1371             im->gdes[i].legend[0] = '\0';
1372
1373         leg_cc = strlen(im->gdes[i].legend);
1374         
1375         /* is there a controle code ant the end of the legend string ? */ 
1376         if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1377             prt_fctn = im->gdes[i].legend[leg_cc-1];
1378             leg_cc -= 2;
1379             im->gdes[i].legend[leg_cc] = '\0';
1380         } else {
1381             prt_fctn = '\0';
1382         }
1383         /* remove exess space */
1384         while (prt_fctn=='g' && 
1385                leg_cc > 0 && 
1386                im->gdes[i].legend[leg_cc-1]==' '){
1387            leg_cc--;
1388            im->gdes[i].legend[leg_cc]='\0';
1389         }
1390         if (leg_cc != 0 ){
1391            legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1392            
1393            if (fill > 0){ 
1394                /* no interleg space if string ends in \g */
1395                fill += legspace[i];
1396             }
1397             if (im->gdes[i].gf != GF_GPRINT && 
1398                 im->gdes[i].gf != GF_COMMENT) { 
1399                 fill += box;       
1400             }
1401            fill += gfx_get_text_width(im->canvas, fill+border,
1402                                       im->text_prop[TEXT_PROP_LEGEND].font,
1403                                       im->text_prop[TEXT_PROP_LEGEND].size,
1404                                       im->tabwidth,
1405                                       im->gdes[i].legend);
1406             leg_c++;
1407         } else {
1408            legspace[i]=0;
1409         }
1410         /* who said there was a special tag ... ?*/
1411         if (prt_fctn=='g') {    
1412            prt_fctn = '\0';
1413         }
1414         if (prt_fctn == '\0') {
1415             if (i == im->gdes_c -1 ) prt_fctn ='l';
1416             
1417             /* is it time to place the legends ? */
1418             if (fill > im->ximg - 2*border){
1419                 if (leg_c > 1) {
1420                     /* go back one */
1421                     i--; 
1422                     fill = fill_last;
1423                     leg_c--;
1424                     prt_fctn = 'j';
1425                 } else {
1426                     prt_fctn = 'l';
1427                 }
1428                 
1429             }
1430         }
1431
1432
1433         if (prt_fctn != '\0'){
1434             leg_x = border;
1435             if (leg_c >= 2 && prt_fctn == 'j') {
1436                 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1437             } else {
1438                 glue = 0;
1439             }
1440             if (prt_fctn =='c') leg_x =  (im->ximg - fill) / 2.0;
1441             if (prt_fctn =='r') leg_x =  im->ximg - fill - border;
1442
1443             for(ii=mark;ii<=i;ii++){
1444                 if(im->gdes[ii].legend[0]=='\0')
1445                     continue;
1446                 im->gdes[ii].leg_x = leg_x;
1447                 im->gdes[ii].leg_y = leg_y;
1448                 leg_x += 
1449                  gfx_get_text_width(im->canvas, leg_x,
1450                                       im->text_prop[TEXT_PROP_LEGEND].font,
1451                                       im->text_prop[TEXT_PROP_LEGEND].size,
1452                                       im->tabwidth,
1453                                       im->gdes[ii].legend) 
1454                    + legspace[ii]
1455                    + glue;
1456                 if (im->gdes[ii].gf != GF_GPRINT && 
1457                     im->gdes[ii].gf != GF_COMMENT) 
1458                     leg_x += box;          
1459             }       
1460             leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1461             if (prt_fctn == 's') leg_y -=  im->text_prop[TEXT_PROP_LEGEND].size*1.2;       
1462             fill = 0;
1463             leg_c = 0;
1464             mark = ii;
1465         }          
1466     }
1467     im->yimg = leg_y;
1468     free(legspace);
1469   }
1470   return 0;
1471 }
1472
1473 /* create a grid on the graph. it determines what to do
1474    from the values of xsize, start and end */
1475
1476 /* the xaxis labels are determined from the number of seconds per pixel
1477    in the requested graph */
1478
1479
1480
1481 int
1482 calc_horizontal_grid(image_desc_t   *im)
1483 {
1484     double   range;
1485     double   scaledrange;
1486     int      pixel,i;
1487     int      gridind;
1488     int      decimals, fractionals;
1489
1490     im->ygrid_scale.labfact=2;
1491     gridind=-1;
1492     range =  im->maxval - im->minval;
1493     scaledrange = range / im->magfact;
1494
1495         /* does the scale of this graph make it impossible to put lines
1496            on it? If so, give up. */
1497         if (isnan(scaledrange)) {
1498                 return 0;
1499         }
1500
1501     /* find grid spaceing */
1502     pixel=1;
1503     if(isnan(im->ygridstep)){
1504         if(im->extra_flags & ALTYGRID) {
1505             /* find the value with max number of digits. Get number of digits */
1506             decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1507             if(decimals <= 0) /* everything is small. make place for zero */
1508                 decimals = 1;
1509             
1510             fractionals = floor(log10(range));
1511             if(fractionals < 0) /* small amplitude. */
1512                 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1513             else
1514                 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1515             im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1516             if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1517                 im->ygrid_scale.gridstep = 0.1;
1518             /* should have at least 5 lines but no more then 15 */
1519             if(range/im->ygrid_scale.gridstep < 5)
1520                 im->ygrid_scale.gridstep /= 10;
1521             if(range/im->ygrid_scale.gridstep > 15)
1522                 im->ygrid_scale.gridstep *= 10;
1523             if(range/im->ygrid_scale.gridstep > 5) {
1524                 im->ygrid_scale.labfact = 1;
1525                 if(range/im->ygrid_scale.gridstep > 8)
1526                     im->ygrid_scale.labfact = 2;
1527             }
1528             else {
1529                 im->ygrid_scale.gridstep /= 5;
1530                 im->ygrid_scale.labfact = 5;
1531             }
1532         }
1533         else {
1534             for(i=0;ylab[i].grid > 0;i++){
1535                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1536                 if (gridind == -1 && pixel > 5) {
1537                     gridind = i;
1538                     break;
1539                 }
1540             }
1541             
1542             for(i=0; i<4;i++) {
1543                if (pixel * ylab[gridind].lfac[i] >=  2 * im->text_prop[TEXT_PROP_AXIS].size) {
1544                   im->ygrid_scale.labfact =  ylab[gridind].lfac[i];
1545                   break;
1546                }                          
1547             } 
1548             
1549             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1550         }
1551     } else {
1552         im->ygrid_scale.gridstep = im->ygridstep;
1553         im->ygrid_scale.labfact = im->ylabfact;
1554     }
1555     return 1;
1556 }
1557
1558 int draw_horizontal_grid(image_desc_t *im)
1559 {
1560     int      i;
1561     double   scaledstep;
1562     char     graph_label[100];
1563     double X0=im->xorigin;
1564     double X1=im->xorigin+im->xsize;
1565    
1566     int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1567     int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1568     scaledstep = im->ygrid_scale.gridstep/im->magfact;
1569     for (i = sgrid; i <= egrid; i++){
1570        double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1571        if ( Y0 >= im->yorigin-im->ysize
1572                  && Y0 <= im->yorigin){       
1573             if(i % im->ygrid_scale.labfact == 0){               
1574                 if (i==0 || im->symbol == ' ') {
1575                     if(scaledstep < 1){
1576                         if(im->extra_flags & ALTYGRID) {
1577                             sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1578                         }
1579                         else {
1580                             sprintf(graph_label,"%4.1f",scaledstep*i);
1581                         }
1582                     } else {
1583                         sprintf(graph_label,"%4.0f",scaledstep*i);
1584                     }
1585                 }else {
1586                     if(scaledstep < 1){
1587                         sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1588                     } else {
1589                         sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1590                     }
1591                 }
1592
1593                gfx_new_text ( im->canvas,
1594                               X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1595                               im->graph_col[GRC_FONT],
1596                               im->text_prop[TEXT_PROP_AXIS].font,
1597                               im->text_prop[TEXT_PROP_AXIS].size,
1598                               im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1599                               graph_label );
1600                gfx_new_dashed_line ( im->canvas,
1601                               X0-2,Y0,
1602                               X1+2,Y0,
1603                               MGRIDWIDTH, im->graph_col[GRC_MGRID],
1604                               im->grid_dash_on, im->grid_dash_off);            
1605                
1606             } else if (!(im->extra_flags & NOMINOR)) {          
1607                gfx_new_dashed_line ( im->canvas,
1608                               X0-1,Y0,
1609                               X1+1,Y0,
1610                               GRIDWIDTH, im->graph_col[GRC_GRID],
1611                               im->grid_dash_on, im->grid_dash_off);            
1612                
1613             }       
1614         }       
1615     } 
1616     return 1;
1617 }
1618
1619 /* logaritmic horizontal grid */
1620 int
1621 horizontal_log_grid(image_desc_t   *im)   
1622 {
1623     double   pixpex;
1624     int      ii,i;
1625     int      minoridx=0, majoridx=0;
1626     char     graph_label[100];
1627     double   X0,X1,Y0;   
1628     double   value, pixperstep, minstep;
1629
1630     /* find grid spaceing */
1631     pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1632
1633         if (isnan(pixpex)) {
1634                 return 0;
1635         }
1636
1637     for(i=0;yloglab[i][0] > 0;i++){
1638         minstep = log10(yloglab[i][0]);
1639         for(ii=1;yloglab[i][ii+1] > 0;ii++){
1640             if(yloglab[i][ii+2]==0){
1641                 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1642                 break;
1643             }
1644         }
1645         pixperstep = pixpex * minstep;
1646         if(pixperstep > 5){minoridx = i;}
1647        if(pixperstep > 2 *  im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1648     }
1649    
1650    X0=im->xorigin;
1651    X1=im->xorigin+im->xsize;
1652     /* paint minor grid */
1653     for (value = pow((double)10, log10(im->minval) 
1654                           - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1655          value  <= im->maxval;
1656          value *= yloglab[minoridx][0]){
1657         if (value < im->minval) continue;
1658         i=0;    
1659         while(yloglab[minoridx][++i] > 0){          
1660            Y0 = ytr(im,value * yloglab[minoridx][i]);
1661            if (Y0 <= im->yorigin - im->ysize) break;
1662            gfx_new_dashed_line ( im->canvas,
1663                           X0-1,Y0,
1664                           X1+1,Y0,
1665                           GRIDWIDTH, im->graph_col[GRC_GRID],
1666                           im->grid_dash_on, im->grid_dash_off);
1667         }
1668     }
1669
1670     /* paint major grid and labels*/
1671     for (value = pow((double)10, log10(im->minval) 
1672                           - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1673          value <= im->maxval;
1674          value *= yloglab[majoridx][0]){
1675         if (value < im->minval) continue;
1676         i=0;    
1677         while(yloglab[majoridx][++i] > 0){          
1678            Y0 = ytr(im,value * yloglab[majoridx][i]);    
1679            if (Y0 <= im->yorigin - im->ysize) break;
1680            gfx_new_dashed_line ( im->canvas,
1681                           X0-2,Y0,
1682                           X1+2,Y0,
1683                           MGRIDWIDTH, im->graph_col[GRC_MGRID],
1684                           im->grid_dash_on, im->grid_dash_off);
1685            
1686            sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1687            gfx_new_text ( im->canvas,
1688                           X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1689                           im->graph_col[GRC_FONT],
1690                           im->text_prop[TEXT_PROP_AXIS].font,
1691                           im->text_prop[TEXT_PROP_AXIS].size,
1692                           im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1693                           graph_label );
1694         } 
1695     }
1696         return 1;
1697 }
1698
1699
1700 void
1701 vertical_grid(
1702     image_desc_t   *im )
1703 {   
1704     int xlab_sel;               /* which sort of label and grid ? */
1705     time_t ti, tilab, timajor;
1706     long factor;
1707     char graph_label[100];
1708     double X0,Y0,Y1; /* points for filled graph and more*/
1709     struct tm tm;
1710
1711     /* the type of time grid is determined by finding
1712        the number of seconds per pixel in the graph */
1713     
1714     
1715     if(im->xlab_user.minsec == -1){
1716         factor=(im->end - im->start)/im->xsize;
1717         xlab_sel=0;
1718         while ( xlab[xlab_sel+1].minsec != -1 
1719                 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1720         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1721         im->xlab_user.gridst = xlab[xlab_sel].gridst;
1722         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1723         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1724         im->xlab_user.labtm = xlab[xlab_sel].labtm;
1725         im->xlab_user.labst = xlab[xlab_sel].labst;
1726         im->xlab_user.precis = xlab[xlab_sel].precis;
1727         im->xlab_user.stst = xlab[xlab_sel].stst;
1728     }
1729     
1730     /* y coords are the same for every line ... */
1731     Y0 = im->yorigin;
1732     Y1 = im->yorigin-im->ysize;
1733    
1734
1735     /* paint the minor grid */
1736     if (!(im->extra_flags & NOMINOR))
1737     {
1738         for(ti = find_first_time(im->start,
1739                                 im->xlab_user.gridtm,
1740                                 im->xlab_user.gridst),
1741             timajor = find_first_time(im->start,
1742                                 im->xlab_user.mgridtm,
1743                                 im->xlab_user.mgridst);
1744             ti < im->end; 
1745             ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1746             ){
1747             /* are we inside the graph ? */
1748             if (ti < im->start || ti > im->end) continue;
1749             while (timajor < ti) {
1750                 timajor = find_next_time(timajor,
1751                         im->xlab_user.mgridtm, im->xlab_user.mgridst);
1752             }
1753             if (ti == timajor) continue; /* skip as falls on major grid line */
1754            X0 = xtr(im,ti);       
1755            gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1756                im->graph_col[GRC_GRID],
1757                im->grid_dash_on, im->grid_dash_off);
1758            
1759         }
1760     }
1761
1762     /* paint the major grid */
1763     for(ti = find_first_time(im->start,
1764                             im->xlab_user.mgridtm,
1765                             im->xlab_user.mgridst);
1766         ti < im->end; 
1767         ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1768         ){
1769         /* are we inside the graph ? */
1770         if (ti < im->start || ti > im->end) continue;
1771        X0 = xtr(im,ti);
1772        gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1773            im->graph_col[GRC_MGRID],
1774            im->grid_dash_on, im->grid_dash_off);
1775        
1776     }
1777     /* paint the labels below the graph */
1778     for(ti = find_first_time(im->start,
1779                             im->xlab_user.labtm,
1780                             im->xlab_user.labst);
1781         ti <= im->end; 
1782         ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1783         ){
1784         tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1785         /* are we inside the graph ? */
1786         if (ti < im->start || ti > im->end) continue;
1787
1788 #if HAVE_STRFTIME
1789         localtime_r(&tilab, &tm);
1790         strftime(graph_label,99,im->xlab_user.stst, &tm);
1791 #else
1792 # error "your libc has no strftime I guess we'll abort the exercise here."
1793 #endif
1794        gfx_new_text ( im->canvas,
1795                       xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1796                       im->graph_col[GRC_FONT],
1797                       im->text_prop[TEXT_PROP_AXIS].font,
1798                       im->text_prop[TEXT_PROP_AXIS].size,
1799                       im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1800                       graph_label );
1801        
1802     }
1803
1804 }
1805
1806
1807 void 
1808 axis_paint(
1809    image_desc_t   *im
1810            )
1811 {   
1812     /* draw x and y axis */
1813     gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1814                       im->xorigin+im->xsize,im->yorigin-im->ysize,
1815                       GRIDWIDTH, im->graph_col[GRC_GRID]);
1816        
1817        gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1818                          im->xorigin+im->xsize,im->yorigin-im->ysize,
1819                          GRIDWIDTH, im->graph_col[GRC_GRID]);
1820    
1821        gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1822                          im->xorigin+im->xsize+4,im->yorigin,
1823                          MGRIDWIDTH, im->graph_col[GRC_GRID]);
1824    
1825        gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1826                          im->xorigin,im->yorigin-im->ysize-4,
1827                          MGRIDWIDTH, im->graph_col[GRC_GRID]);
1828    
1829     
1830     /* arrow for X axis direction */
1831     gfx_new_area ( im->canvas, 
1832                    im->xorigin+im->xsize+3,  im->yorigin-3,
1833                    im->xorigin+im->xsize+3,  im->yorigin+4,
1834                    im->xorigin+im->xsize+8,  im->yorigin+0.5, /* LINEOFFSET */
1835                    im->graph_col[GRC_ARROW]);
1836    
1837    
1838    
1839 }
1840
1841 void
1842 grid_paint(image_desc_t   *im)
1843 {   
1844     long i;
1845     int res=0;
1846     double X0,Y0; /* points for filled graph and more*/
1847     gfx_node_t *node;
1848
1849     /* draw 3d border */
1850     node = gfx_new_area (im->canvas, 0,im->yimg,
1851                                  2,im->yimg-2,
1852                                  2,2,im->graph_col[GRC_SHADEA]);
1853     gfx_add_point( node , im->ximg - 2, 2 );
1854     gfx_add_point( node , im->ximg, 0 );
1855     gfx_add_point( node , 0,0 );
1856 /*    gfx_add_point( node , 0,im->yimg ); */
1857    
1858     node =  gfx_new_area (im->canvas, 2,im->yimg-2,
1859                                   im->ximg-2,im->yimg-2,
1860                                   im->ximg - 2, 2,
1861                                  im->graph_col[GRC_SHADEB]);
1862     gfx_add_point( node ,   im->ximg,0);
1863     gfx_add_point( node ,   im->ximg,im->yimg);
1864     gfx_add_point( node ,   0,im->yimg);
1865 /*    gfx_add_point( node , 0,im->yimg ); */
1866    
1867    
1868     if (im->draw_x_grid == 1 )
1869       vertical_grid(im);
1870     
1871     if (im->draw_y_grid == 1){
1872         if(im->logarithmic){
1873                 res = horizontal_log_grid(im);
1874         } else {
1875                 res = draw_horizontal_grid(im);
1876         }
1877
1878         /* dont draw horizontal grid if there is no min and max val */
1879         if (! res ) {
1880           char *nodata = "No Data found";
1881            gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1882                         im->graph_col[GRC_FONT],
1883                         im->text_prop[TEXT_PROP_AXIS].font,
1884                         im->text_prop[TEXT_PROP_AXIS].size,
1885                         im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1886                         nodata );          
1887         }
1888     }
1889
1890     /* yaxis description */
1891         if (im->canvas->imgformat != IF_PNG) {
1892             gfx_new_text( im->canvas,
1893                           7, (im->yorigin - im->ysize/2),
1894                           im->graph_col[GRC_FONT],
1895                           im->text_prop[TEXT_PROP_AXIS].font,
1896                           im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1897                           GFX_H_CENTER, GFX_V_CENTER,
1898                           im->ylegend);
1899         } else {
1900             /* horrible hack until we can actually print vertically */
1901             {
1902                 int n;
1903                 int l=strlen(im->ylegend);
1904                 char s[2];
1905                 for (n=0;n<strlen(im->ylegend);n++) {
1906                     s[0]=im->ylegend[n];
1907                     s[1]='\0';
1908                     gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1909                         im->graph_col[GRC_FONT],
1910                         im->text_prop[TEXT_PROP_AXIS].font,
1911                         im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1912                         GFX_H_CENTER, GFX_V_CENTER,
1913                         s);
1914                 }
1915             }
1916         }
1917    
1918     /* graph title */
1919     gfx_new_text( im->canvas,
1920                   im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1921                   im->graph_col[GRC_FONT],
1922                   im->text_prop[TEXT_PROP_TITLE].font,
1923                   im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1924                   GFX_H_CENTER, GFX_V_CENTER,
1925                   im->title);
1926
1927     /* graph labels */
1928     if( !(im->extra_flags & NOLEGEND) ) {
1929       for(i=0;i<im->gdes_c;i++){
1930         if(im->gdes[i].legend[0] =='\0')
1931             continue;
1932          
1933         /* im->gdes[i].leg_y is the bottom of the legend */
1934                 X0 = im->gdes[i].leg_x;
1935                 Y0 = im->gdes[i].leg_y;
1936                 /* Box needed? */
1937                 if (       im->gdes[i].gf != GF_GPRINT
1938                         && im->gdes[i].gf != GF_COMMENT) {
1939                     int boxH, boxV;
1940
1941                     boxH = gfx_get_text_width(im->canvas, 0,
1942                                 im->text_prop[TEXT_PROP_AXIS].font,
1943                                 im->text_prop[TEXT_PROP_AXIS].size,
1944                                 im->tabwidth,"M") * 1.25;
1945                     boxV = boxH;
1946
1947                     node = gfx_new_area(im->canvas,
1948                                 X0,Y0-boxV,
1949                                 X0,Y0,
1950                                 X0+boxH,Y0,
1951                                 im->gdes[i].col);
1952                     gfx_add_point ( node, X0+boxH, Y0-boxV );
1953                     node = gfx_new_line(im->canvas,
1954                                 X0,Y0-boxV, X0,Y0,
1955                                 1,0x000000FF);
1956                     gfx_add_point(node,X0+boxH,Y0);
1957                     gfx_add_point(node,X0+boxH,Y0-boxV);
1958                     gfx_close_path(node);
1959                     X0 += boxH / 1.25 * 2;
1960                 }
1961                 gfx_new_text ( im->canvas, X0, Y0,
1962                                    im->graph_col[GRC_FONT],
1963                                    im->text_prop[TEXT_PROP_AXIS].font,
1964                                    im->text_prop[TEXT_PROP_AXIS].size,
1965                                    im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1966                                    im->gdes[i].legend );
1967               }
1968            }
1969         }
1970
1971
1972 /*****************************************************
1973  * lazy check make sure we rely need to create this graph
1974  *****************************************************/
1975
1976 int lazy_check(image_desc_t *im){
1977     FILE *fd = NULL;
1978         int size = 1;
1979     struct stat  imgstat;
1980     
1981     if (im->lazy == 0) return 0; /* no lazy option */
1982     if (stat(im->graphfile,&imgstat) != 0) 
1983       return 0; /* can't stat */
1984     /* one pixel in the existing graph is more then what we would
1985        change here ... */
1986     if (time(NULL) - imgstat.st_mtime > 
1987         (im->end - im->start) / im->xsize) 
1988       return 0;
1989     if ((fd = fopen(im->graphfile,"rb")) == NULL) 
1990       return 0; /* the file does not exist */
1991     switch (im->canvas->imgformat) {
1992     case IF_PNG:
1993            size = PngSize(fd,&(im->ximg),&(im->yimg));
1994            break;
1995     default:
1996            size = 1;
1997     }
1998     fclose(fd);
1999     return size;
2000 }
2001
2002 void
2003 pie_part(image_desc_t *im, gfx_color_t color,
2004             double PieCenterX, double PieCenterY, double Radius,
2005             double startangle, double endangle)
2006 {
2007     gfx_node_t *node;
2008     double angle;
2009     double step=M_PI/50; /* Number of iterations for the circle;
2010                          ** 10 is definitely too low, more than
2011                          ** 50 seems to be overkill
2012                          */
2013
2014     /* Strange but true: we have to work clockwise or else
2015     ** anti aliasing nor transparency don't work.
2016     **
2017     ** This test is here to make sure we do it right, also
2018     ** this makes the for...next loop more easy to implement.
2019     ** The return will occur if the user enters a negative number
2020     ** (which shouldn't be done according to the specs) or if the
2021     ** programmers do something wrong (which, as we all know, never
2022     ** happens anyway :)
2023     */
2024     if (endangle<startangle) return;
2025
2026     /* Hidden feature: Radius decreases each full circle */
2027     angle=startangle;
2028     while (angle>=2*M_PI) {
2029         angle  -= 2*M_PI;
2030         Radius *= 0.8;
2031     }
2032
2033     node=gfx_new_area(im->canvas,
2034                 PieCenterX+sin(startangle)*Radius,
2035                 PieCenterY-cos(startangle)*Radius,
2036                 PieCenterX,
2037                 PieCenterY,
2038                 PieCenterX+sin(endangle)*Radius,
2039                 PieCenterY-cos(endangle)*Radius,
2040                 color);
2041     for (angle=endangle;angle-startangle>=step;angle-=step) {
2042         gfx_add_point(node,
2043                 PieCenterX+sin(angle)*Radius,
2044                 PieCenterY-cos(angle)*Radius );
2045     }
2046 }
2047
2048 int
2049 graph_size_location(image_desc_t *im, int elements, int piechart )
2050 {
2051     /* The actual size of the image to draw is determined from
2052     ** several sources.  The size given on the command line is
2053     ** the graph area but we need more as we have to draw labels
2054     ** and other things outside the graph area
2055     */
2056
2057     /* +-+-------------------------------------------+
2058     ** |l|.................title.....................|
2059     ** |e+--+-------------------------------+--------+
2060     ** |b| b|                               |        |
2061     ** |a| a|                               |  pie   |
2062     ** |l| l|          main graph area      | chart  |
2063     ** |.| .|                               |  area  |
2064     ** |t| y|                               |        |
2065     ** |r+--+-------------------------------+--------+
2066     ** |e|  | x-axis labels                 |        |
2067     ** |v+--+-------------------------------+--------+
2068     ** | |..............legends......................|
2069     ** +-+-------------------------------------------+
2070     */
2071     int Xvertical=0,    Yvertical=0,
2072         Xtitle   =0,    Ytitle   =0,
2073         Xylabel  =0,    Yylabel  =0,
2074         Xmain    =0,    Ymain    =0,
2075         Xpie     =0,    Ypie     =0,
2076         Xxlabel  =0,    Yxlabel  =0,
2077 #if 0
2078         Xlegend  =0,    Ylegend  =0,
2079 #endif
2080         Xspacing =10,   Yspacing =10;
2081
2082     if (im->ylegend[0] != '\0') {
2083         Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2084         Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2085     }
2086
2087     if (im->title[0] != '\0') {
2088         /* The title is placed "inbetween" two text lines so it
2089         ** automatically has some vertical spacing.  The horizontal
2090         ** spacing is added here, on each side.
2091         */
2092         Xtitle = gfx_get_text_width(im->canvas, 0,
2093                 im->text_prop[TEXT_PROP_TITLE].font,
2094                 im->text_prop[TEXT_PROP_TITLE].size,
2095                 im->tabwidth,
2096                 im->title) + 2*Xspacing;
2097         Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2098     }
2099
2100     if (elements) {
2101         Xmain=im->xsize;
2102         Ymain=im->ysize;
2103         if (im->draw_x_grid) {
2104             Xxlabel=Xmain;
2105             Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2106         }
2107         if (im->draw_y_grid) {
2108             Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2109             Yylabel=Ymain;
2110         }
2111     }
2112
2113     if (piechart) {
2114         im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2115         Xpie=im->piesize;
2116         Ypie=im->piesize;
2117     }
2118
2119     /* Now calculate the total size.  Insert some spacing where
2120        desired.  im->xorigin and im->yorigin need to correspond
2121        with the lower left corner of the main graph area or, if
2122        this one is not set, the imaginary box surrounding the
2123        pie chart area. */
2124
2125     /* The legend width cannot yet be determined, as a result we
2126     ** have problems adjusting the image to it.  For now, we just
2127     ** forget about it at all; the legend will have to fit in the
2128     ** size already allocated.
2129     */
2130     im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2131     if (Xmain) im->ximg += Xspacing;
2132     if (Xpie) im->ximg += Xspacing;
2133     im->xorigin = Xspacing + Xylabel;
2134     if (Xtitle > im->ximg) im->ximg = Xtitle;
2135     if (Xvertical) {
2136         im->ximg += Xvertical;
2137         im->xorigin += Xvertical;
2138     }
2139     xtr(im,0);
2140
2141     /* The vertical size is interesting... we need to compare
2142     ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2143     ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2144     ** start even thinking about Ylegend.
2145     **
2146     ** Do it in three portions: First calculate the inner part,
2147     ** then do the legend, then adjust the total height of the img.
2148     */
2149
2150     /* reserve space for main and/or pie */
2151     im->yimg = Ymain + Yxlabel;
2152     if (im->yimg < Ypie) im->yimg = Ypie;
2153     im->yorigin = im->yimg - Yxlabel;
2154     /* reserve space for the title *or* some padding above the graph */
2155     if (Ytitle) {
2156         im->yimg += Ytitle;
2157         im->yorigin += Ytitle;
2158     } else {
2159         im->yimg += Yspacing;
2160         im->yorigin += Yspacing;
2161     }
2162     /* reserve space for padding below the graph */
2163     im->yimg += Yspacing;
2164     ytr(im,DNAN);
2165
2166     /* Determine where to place the legends onto the image.
2167     ** Adjust im->yimg to match the space requirements.
2168     */
2169     if(leg_place(im)==-1)
2170         return -1;
2171
2172     /* last of three steps: check total height of image */
2173     if (im->yimg < Yvertical) im->yimg = Yvertical;
2174
2175 #if 0
2176     if (Xlegend > im->ximg) {
2177         im->ximg = Xlegend;
2178         /* reposition Pie */
2179     }
2180 #endif
2181
2182     /* The pie is placed in the upper right hand corner,
2183     ** just below the title (if any) and with sufficient
2184     ** padding.
2185     */
2186     if (elements) {
2187         im->pie_x = im->ximg - Xspacing - Xpie/2;
2188         im->pie_y = im->yorigin-Ymain+Ypie/2;
2189     } else {
2190         im->pie_x = im->ximg/2;
2191         im->pie_y = im->yorigin-Ypie/2;
2192     }
2193
2194     return 0;
2195 }
2196
2197 /* draw that picture thing ... */
2198 int
2199 graph_paint(image_desc_t *im, char ***calcpr)
2200 {
2201   int i,ii;
2202   int lazy =     lazy_check(im);
2203   int piechart = 0;
2204   double PieStart=0.0;
2205   FILE  *fo;
2206   gfx_node_t *node;
2207   
2208   double areazero = 0.0;
2209   enum gf_en stack_gf = GF_PRINT;
2210   graph_desc_t *lastgdes = NULL;    
2211
2212   /* if we are lazy and there is nothing to PRINT ... quit now */
2213   if (lazy && im->prt_c==0) return 0;
2214
2215   /* pull the data from the rrd files ... */
2216   
2217   if(data_fetch(im)==-1)
2218     return -1;
2219
2220   /* evaluate VDEF and CDEF operations ... */
2221   if(data_calc(im)==-1)
2222     return -1;
2223   
2224   /* check if we need to draw a piechart */
2225   for(i=0;i<im->gdes_c;i++){
2226     if (im->gdes[i].gf == GF_PART) {
2227       piechart=1;
2228       break;
2229     }
2230   }
2231
2232   /* calculate and PRINT and GPRINT definitions. We have to do it at
2233    * this point because it will affect the length of the legends
2234    * if there are no graph elements we stop here ... 
2235    * if we are lazy, try to quit ... 
2236    */
2237   i=print_calc(im,calcpr);
2238   if(i<0) return -1;
2239   if(((i==0)&&(piechart==0)) || lazy) return 0;
2240
2241   /* If there's only the pie chart to draw, signal this */
2242   if (i==0) piechart=2;
2243   
2244   /* get actual drawing data and find min and max values*/
2245   if(data_proc(im)==-1)
2246     return -1;
2247   
2248   if(!im->logarithmic){si_unit(im);}        /* identify si magnitude Kilo, Mega Giga ? */
2249   
2250   if(!im->rigid && ! im->logarithmic)
2251     expand_range(im);   /* make sure the upper and lower limit are
2252                            sensible values */
2253
2254   if (!calc_horizontal_grid(im))
2255     return -1;
2256   if (im->gridfit)
2257     apply_gridfit(im);
2258
2259 /**************************************************************
2260  *** Calculating sizes and locations became a bit confusing ***
2261  *** so I moved this into a separate function.              ***
2262  **************************************************************/
2263   if(graph_size_location(im,i,piechart)==-1)
2264     return -1;
2265
2266   /* the actual graph is created by going through the individual
2267      graph elements and then drawing them */
2268   
2269   node=gfx_new_area ( im->canvas,
2270                       0, 0,
2271                       im->ximg, 0,
2272                       im->ximg, im->yimg,
2273                       im->graph_col[GRC_BACK]);
2274
2275   gfx_add_point(node,0, im->yimg);
2276
2277   if (piechart != 2) {
2278     node=gfx_new_area ( im->canvas,
2279                       im->xorigin,             im->yorigin, 
2280                       im->xorigin + im->xsize, im->yorigin,
2281                       im->xorigin + im->xsize, im->yorigin-im->ysize,
2282                       im->graph_col[GRC_CANVAS]);
2283   
2284     gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2285
2286     if (im->minval > 0.0)
2287       areazero = im->minval;
2288     if (im->maxval < 0.0)
2289       areazero = im->maxval;
2290   
2291     axis_paint(im);
2292   }
2293
2294   if (piechart) {
2295     pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2296   }
2297
2298   for(i=0;i<im->gdes_c;i++){
2299     switch(im->gdes[i].gf){
2300     case GF_CDEF:
2301     case GF_VDEF:
2302     case GF_DEF:
2303     case GF_PRINT:
2304     case GF_GPRINT:
2305     case GF_COMMENT:
2306     case GF_HRULE:
2307     case GF_VRULE:
2308     case GF_XPORT:
2309       break;
2310     case GF_TICK:
2311       for (ii = 0; ii < im->xsize; ii++)
2312         {
2313           if (!isnan(im->gdes[i].p_data[ii]) && 
2314               im->gdes[i].p_data[ii] > 0.0)
2315             { 
2316               /* generate a tick */
2317               gfx_new_line(im->canvas, im -> xorigin + ii, 
2318                            im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2319                            im -> xorigin + ii, 
2320                            im -> yorigin,
2321                            1.0,
2322                            im -> gdes[i].col );
2323             }
2324         }
2325       break;
2326     case GF_LINE:
2327     case GF_AREA:
2328       stack_gf = im->gdes[i].gf;
2329     case GF_STACK:          
2330       /* fix data points at oo and -oo */
2331       for(ii=0;ii<im->xsize;ii++){
2332         if (isinf(im->gdes[i].p_data[ii])){
2333           if (im->gdes[i].p_data[ii] > 0) {
2334             im->gdes[i].p_data[ii] = im->maxval ;
2335           } else {
2336             im->gdes[i].p_data[ii] = im->minval ;
2337           }                 
2338           
2339         }
2340       } /* for */
2341       
2342       if (im->gdes[i].col != 0x0){               
2343         /* GF_LINE and friend */
2344         if(stack_gf == GF_LINE ){
2345           node = NULL;
2346           for(ii=1;ii<im->xsize;ii++){
2347             if ( ! isnan(im->gdes[i].p_data[ii-1])
2348                  && ! isnan(im->gdes[i].p_data[ii])){
2349               if (node == NULL){
2350                 node = gfx_new_line(im->canvas,
2351                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2352                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2353                                     im->gdes[i].linewidth,
2354                                     im->gdes[i].col);
2355               } else {
2356                 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2357               }
2358             } else {
2359               node = NULL;
2360             }
2361           }
2362         } else {
2363           int area_start=-1;
2364           node = NULL;
2365           for(ii=1;ii<im->xsize;ii++){
2366             /* open an area */
2367             if ( ! isnan(im->gdes[i].p_data[ii-1])
2368                  && ! isnan(im->gdes[i].p_data[ii])){
2369               if (node == NULL){
2370                 float ybase = 0.0;
2371 /*
2372                 if (im->gdes[i].gf == GF_STACK) {
2373 */
2374                 if ( (im->gdes[i].gf == GF_STACK)
2375                   || (im->gdes[i].stack) ) {
2376
2377                   ybase = ytr(im,lastgdes->p_data[ii-1]);
2378                 } else {
2379                   ybase =  ytr(im,areazero);
2380                 }
2381                 area_start = ii-1;
2382                 node = gfx_new_area(im->canvas,
2383                                     ii-1+im->xorigin,ybase,
2384                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2385                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2386                                     im->gdes[i].col
2387                                     );
2388               } else {
2389                 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2390               }
2391             }
2392
2393             if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2394               /* GF_AREA STACK type*/
2395 /*
2396               if (im->gdes[i].gf == GF_STACK ) {
2397 */
2398               if ( (im->gdes[i].gf == GF_STACK)
2399                 || (im->gdes[i].stack) ) {
2400                 int iii;
2401                 for (iii=ii-1;iii>area_start;iii--){
2402                   gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2403                 }
2404               } else {
2405                 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2406               };
2407               node=NULL;
2408             };
2409           }             
2410         } /* else GF_LINE */
2411       } /* if color != 0x0 */
2412       /* make sure we do not run into trouble when stacking on NaN */
2413       for(ii=0;ii<im->xsize;ii++){
2414         if (isnan(im->gdes[i].p_data[ii])) {
2415           if (lastgdes && (im->gdes[i].gf == GF_STACK)) {
2416             im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2417           } else {
2418             im->gdes[i].p_data[ii] =  ytr(im,areazero);
2419           }
2420         }
2421       } 
2422       lastgdes = &(im->gdes[i]);                         
2423       break;
2424     case GF_PART:
2425       if(isnan(im->gdes[i].yrule)) /* fetch variable */
2426         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2427      
2428       if (finite(im->gdes[i].yrule)) {  /* even the fetched var can be NaN */
2429         pie_part(im,im->gdes[i].col,
2430                 im->pie_x,im->pie_y,im->piesize*0.4,
2431                 M_PI*2.0*PieStart/100.0,
2432                 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2433         PieStart += im->gdes[i].yrule;
2434       }
2435       break;
2436     } /* switch */
2437   }
2438   if (piechart==2) {
2439     im->draw_x_grid=0;
2440     im->draw_y_grid=0;
2441   }
2442   /* grid_paint also does the text */
2443   grid_paint(im);
2444   
2445   /* the RULES are the last thing to paint ... */
2446   for(i=0;i<im->gdes_c;i++){    
2447     
2448     switch(im->gdes[i].gf){
2449     case GF_HRULE:
2450       if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2451         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2452       };
2453       if(im->gdes[i].yrule >= im->minval
2454          && im->gdes[i].yrule <= im->maxval)
2455         gfx_new_line(im->canvas,
2456                      im->xorigin,ytr(im,im->gdes[i].yrule),
2457                      im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2458                      1.0,im->gdes[i].col); 
2459       break;
2460     case GF_VRULE:
2461       if(im->gdes[i].xrule == 0) { /* fetch variable */
2462         im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2463       };
2464       if(im->gdes[i].xrule >= im->start
2465          && im->gdes[i].xrule <= im->end)
2466         gfx_new_line(im->canvas,
2467                      xtr(im,im->gdes[i].xrule),im->yorigin,
2468                      xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2469                      1.0,im->gdes[i].col); 
2470       break;
2471     default:
2472       break;
2473     }
2474   }
2475
2476   
2477   if (strcmp(im->graphfile,"-")==0) {
2478 #ifdef WIN32
2479     /* Change translation mode for stdout to BINARY */
2480     _setmode( _fileno( stdout ), O_BINARY );
2481 #endif
2482     fo = stdout;
2483   } else {
2484     if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2485       rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2486                     rrd_strerror(errno));
2487       return (-1);
2488     }
2489   }
2490   gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2491   if (strcmp(im->graphfile,"-") != 0)
2492     fclose(fo);
2493   return 0;
2494 }
2495
2496
2497 /*****************************************************
2498  * graph stuff 
2499  *****************************************************/
2500
2501 int
2502 gdes_alloc(image_desc_t *im){
2503
2504     unsigned long def_step = (im->end-im->start)/im->xsize;
2505     
2506     if (im->step > def_step) /* step can be increassed ... no decreassed */
2507       def_step = im->step;
2508
2509     im->gdes_c++;
2510     
2511     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2512                                            * sizeof(graph_desc_t)))==NULL){
2513         rrd_set_error("realloc graph_descs");
2514         return -1;
2515     }
2516
2517
2518     im->gdes[im->gdes_c-1].step=def_step; 
2519     im->gdes[im->gdes_c-1].stack=0;
2520     im->gdes[im->gdes_c-1].debug=0;
2521     im->gdes[im->gdes_c-1].start=im->start; 
2522     im->gdes[im->gdes_c-1].end=im->end; 
2523     im->gdes[im->gdes_c-1].vname[0]='\0'; 
2524     im->gdes[im->gdes_c-1].data=NULL;
2525     im->gdes[im->gdes_c-1].ds_namv=NULL;
2526     im->gdes[im->gdes_c-1].data_first=0;
2527     im->gdes[im->gdes_c-1].p_data=NULL;
2528     im->gdes[im->gdes_c-1].rpnp=NULL;
2529     im->gdes[im->gdes_c-1].col = 0x0;
2530     im->gdes[im->gdes_c-1].legend[0]='\0';
2531     im->gdes[im->gdes_c-1].rrd[0]='\0';
2532     im->gdes[im->gdes_c-1].ds=-1;    
2533     im->gdes[im->gdes_c-1].p_data=NULL;    
2534     im->gdes[im->gdes_c-1].yrule=DNAN;
2535     im->gdes[im->gdes_c-1].xrule=0;
2536     return 0;
2537 }
2538
2539 /* copies input untill the first unescaped colon is found
2540    or until input ends. backslashes have to be escaped as well */
2541 int
2542 scan_for_col(char *input, int len, char *output)
2543 {
2544     int inp,outp=0;
2545     for (inp=0; 
2546          inp < len &&
2547            input[inp] != ':' &&
2548            input[inp] != '\0';
2549          inp++){
2550       if (input[inp] == '\\' &&
2551           input[inp+1] != '\0' && 
2552           (input[inp+1] == '\\' ||
2553            input[inp+1] == ':')){
2554         output[outp++] = input[++inp];
2555       }
2556       else {
2557         output[outp++] = input[inp];
2558       }
2559     }
2560     output[outp] = '\0';
2561     return inp;
2562 }
2563 /* Some surgery done on this function, it became ridiculously big.
2564 ** Things moved:
2565 ** - initializing     now in rrd_graph_init()
2566 ** - options parsing  now in rrd_graph_options()
2567 ** - script parsing   now in rrd_graph_script()
2568 */
2569 int 
2570 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2571 {
2572     image_desc_t   im;
2573             
2574     rrd_graph_init(&im);
2575
2576     rrd_graph_options(argc,argv,&im);
2577     if (rrd_test_error()) {
2578         im_free(&im);
2579         return -1;
2580     }
2581     
2582     if (strlen(argv[optind])>=MAXPATH) {
2583         rrd_set_error("filename (including path) too long");
2584         im_free(&im);
2585         return -1;
2586     }
2587     strncpy(im.graphfile,argv[optind],MAXPATH-1);
2588     im.graphfile[MAXPATH-1]='\0';
2589
2590     rrd_graph_script(argc,argv,&im);
2591     if (rrd_test_error()) {
2592         im_free(&im);
2593         return -1;
2594     }
2595
2596     /* Everything is now read and the actual work can start */
2597
2598     (*prdata)=NULL;
2599     if (graph_paint(&im,prdata)==-1){
2600         im_free(&im);
2601         return -1;
2602     }
2603
2604     /* The image is generated and needs to be output.
2605     ** Also, if needed, print a line with information about the image.
2606     */
2607
2608     *xsize=im.ximg;
2609     *ysize=im.yimg;
2610     if (im.imginfo) {
2611         char *filename;
2612         if (!(*prdata)) {
2613             /* maybe prdata is not allocated yet ... lets do it now */
2614             if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2615                 rrd_set_error("malloc imginfo");
2616                 return -1; 
2617             };
2618         }
2619         if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2620          ==NULL){
2621             rrd_set_error("malloc imginfo");
2622             return -1;
2623         }
2624         filename=im.graphfile+strlen(im.graphfile);
2625         while(filename > im.graphfile) {
2626             if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2627             filename--;
2628         }
2629
2630         sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2631     }
2632     im_free(&im);
2633     return 0;
2634 }
2635
2636 void
2637 rrd_graph_init(image_desc_t *im)
2638 {
2639     unsigned int i;
2640
2641 #ifdef HAVE_TZSET
2642     tzset();
2643 #endif
2644 #ifdef HAVE_SETLOCALE
2645     setlocale(LC_TIME,"");
2646 #endif
2647
2648     im->xlab_user.minsec = -1;
2649     im->ximg=0;
2650     im->yimg=0;
2651     im->xsize = 400;
2652     im->ysize = 100;
2653     im->step = 0;
2654     im->ylegend[0] = '\0';
2655     im->title[0] = '\0';
2656     im->minval = DNAN;
2657     im->maxval = DNAN;    
2658     im->unitsexponent= 9999;
2659     im->extra_flags= 0;
2660     im->rigid = 0;
2661     im->gridfit = 1;
2662     im->imginfo = NULL;
2663     im->lazy = 0;
2664     im->logarithmic = 0;
2665     im->ygridstep = DNAN;
2666     im->draw_x_grid = 1;
2667     im->draw_y_grid = 1;
2668     im->base = 1000;
2669     im->prt_c = 0;
2670     im->gdes_c = 0;
2671     im->gdes = NULL;
2672     im->canvas = gfx_new_canvas();
2673     im->grid_dash_on = 1;
2674     im->grid_dash_off = 1;
2675
2676     for(i=0;i<DIM(graph_col);i++)
2677         im->graph_col[i]=graph_col[i];
2678 #ifdef WIN32
2679     {
2680     char *windir; 
2681     windir = getenv("windir");
2682     /* %windir% is something like D:\windows or C:\winnt */
2683     if (windir != NULL) {
2684         strcpy(rrd_win_default_font,windir);
2685         strcat(rrd_win_default_font,"\\fonts\\cour.ttf");
2686         for(i=0;i<DIM(text_prop);i++)
2687            text_prop[i].font = rrd_win_default_font;
2688     }
2689     }
2690 #endif
2691     for(i=0;i<DIM(text_prop);i++){        
2692       im->text_prop[i].size = text_prop[i].size;
2693       im->text_prop[i].font = text_prop[i].font;
2694     }
2695 }
2696
2697 void
2698 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2699 {
2700     int                 stroff;    
2701     char                *parsetime_error = NULL;
2702     char                scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2703     time_t              start_tmp=0,end_tmp=0;
2704     long                long_tmp;
2705     struct time_value   start_tv, end_tv;
2706     gfx_color_t         color;
2707
2708     parsetime("end-24h", &start_tv);
2709     parsetime("now", &end_tv);
2710
2711     while (1){
2712         static struct option long_options[] =
2713         {
2714             {"start",      required_argument, 0,  's'},
2715             {"end",        required_argument, 0,  'e'},
2716             {"x-grid",     required_argument, 0,  'x'},
2717             {"y-grid",     required_argument, 0,  'y'},
2718             {"vertical-label",required_argument,0,'v'},
2719             {"width",      required_argument, 0,  'w'},
2720             {"height",     required_argument, 0,  'h'},
2721             {"interlaced", no_argument,       0,  'i'},
2722             {"upper-limit",required_argument, 0,  'u'},
2723             {"lower-limit",required_argument, 0,  'l'},
2724             {"rigid",      no_argument,       0,  'r'},
2725             {"base",       required_argument, 0,  'b'},
2726             {"logarithmic",no_argument,       0,  'o'},
2727             {"color",      required_argument, 0,  'c'},
2728             {"font",       required_argument, 0,  'n'},
2729             {"title",      required_argument, 0,  't'},
2730             {"imginfo",    required_argument, 0,  'f'},
2731             {"imgformat",  required_argument, 0,  'a'},
2732             {"lazy",       no_argument,       0,  'z'},
2733             {"zoom",       required_argument, 0,  'm'},
2734             {"no-legend",  no_argument,       0,  'g'},
2735             {"alt-y-grid", no_argument,       0,  'Y'},
2736             {"no-minor",   no_argument,       0,  'I'},
2737             {"alt-autoscale", no_argument,    0,  'A'},
2738             {"alt-autoscale-max", no_argument, 0, 'M'},
2739             {"units-exponent",required_argument, 0, 'X'},
2740             {"step",       required_argument, 0,    'S'},
2741             {"no-gridfit", no_argument,       0,   'N'},
2742             {0,0,0,0}};
2743         int option_index = 0;
2744         int opt;
2745
2746
2747         opt = getopt_long(argc, argv, 
2748                           "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgYAMX:S:N",
2749                           long_options, &option_index);
2750
2751         if (opt == EOF)
2752             break;
2753         
2754         switch(opt) {
2755         case 'I':
2756             im->extra_flags |= NOMINOR;
2757             break;
2758         case 'Y':
2759             im->extra_flags |= ALTYGRID;
2760             break;
2761         case 'A':
2762             im->extra_flags |= ALTAUTOSCALE;
2763             break;
2764         case 'M':
2765             im->extra_flags |= ALTAUTOSCALE_MAX;
2766             break;
2767         case 'g':
2768             im->extra_flags |= NOLEGEND;
2769             break;
2770         case 'X':
2771             im->unitsexponent = atoi(optarg);
2772             break;
2773         case 'S':
2774             im->step =  atoi(optarg);
2775             break;
2776         case 262:
2777             im->gridfit = 0;
2778             break;
2779         case 's':
2780             if ((parsetime_error = parsetime(optarg, &start_tv))) {
2781                 rrd_set_error( "start time: %s", parsetime_error );
2782                 return;
2783             }
2784             break;
2785         case 'e':
2786             if ((parsetime_error = parsetime(optarg, &end_tv))) {
2787                 rrd_set_error( "end time: %s", parsetime_error );
2788                 return;
2789             }
2790             break;
2791         case 'x':
2792             if(strcmp(optarg,"none") == 0){
2793               im->draw_x_grid=0;
2794               break;
2795             };
2796                 
2797             if(sscanf(optarg,
2798                       "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2799                       scan_gtm,
2800                       &im->xlab_user.gridst,
2801                       scan_mtm,
2802                       &im->xlab_user.mgridst,
2803                       scan_ltm,
2804                       &im->xlab_user.labst,
2805                       &im->xlab_user.precis,
2806                       &stroff) == 7 && stroff != 0){
2807                 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2808                 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2809                     rrd_set_error("unknown keyword %s",scan_gtm);
2810                     return;
2811                 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2812                     rrd_set_error("unknown keyword %s",scan_mtm);
2813                     return;
2814                 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2815                     rrd_set_error("unknown keyword %s",scan_ltm);
2816                     return;
2817                 } 
2818                 im->xlab_user.minsec = 1;
2819                 im->xlab_user.stst = im->xlab_form;
2820             } else {
2821                 rrd_set_error("invalid x-grid format");
2822                 return;
2823             }
2824             break;
2825         case 'y':
2826
2827             if(strcmp(optarg,"none") == 0){
2828               im->draw_y_grid=0;
2829               break;
2830             };
2831
2832             if(sscanf(optarg,
2833                       "%lf:%d",
2834                       &im->ygridstep,
2835                       &im->ylabfact) == 2) {
2836                 if(im->ygridstep<=0){
2837                     rrd_set_error("grid step must be > 0");
2838                     return;
2839                 } else if (im->ylabfact < 1){
2840                     rrd_set_error("label factor must be > 0");
2841                     return;
2842                 } 
2843             } else {
2844                 rrd_set_error("invalid y-grid format");
2845                 return;
2846             }
2847             break;
2848         case 'v':
2849             strncpy(im->ylegend,optarg,150);
2850             im->ylegend[150]='\0';
2851             break;
2852         case 'u':
2853             im->maxval = atof(optarg);
2854             break;
2855         case 'l':
2856             im->minval = atof(optarg);
2857             break;
2858         case 'b':
2859             im->base = atol(optarg);
2860             if(im->base != 1024 && im->base != 1000 ){
2861                 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2862                 return;
2863             }
2864             break;
2865         case 'w':
2866             long_tmp = atol(optarg);
2867             if (long_tmp < 10) {
2868                 rrd_set_error("width below 10 pixels");
2869                 return;
2870             }
2871             im->xsize = long_tmp;
2872             break;
2873         case 'h':
2874             long_tmp = atol(optarg);
2875             if (long_tmp < 10) {
2876                 rrd_set_error("height below 10 pixels");
2877                 return;
2878             }
2879             im->ysize = long_tmp;
2880             break;
2881         case 'i':
2882             im->canvas->interlaced = 1;
2883             break;
2884         case 'r':
2885             im->rigid = 1;
2886             break;
2887         case 'f':
2888             im->imginfo = optarg;
2889             break;
2890         case 'a':
2891             if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2892                 rrd_set_error("unsupported graphics format '%s'",optarg);
2893                 return;
2894             }
2895             break;
2896         case 'z':
2897             im->lazy = 1;
2898             break;
2899         case 'o':
2900             im->logarithmic = 1;
2901             if (isnan(im->minval))
2902                 im->minval=1;
2903             break;
2904         case 'c':
2905             if(sscanf(optarg,
2906                       "%10[A-Z]#%8lx",
2907                       col_nam,&color) == 2){
2908                 int ci;
2909                 if((ci=grc_conv(col_nam)) != -1){
2910                     im->graph_col[ci]=color;
2911                 }  else {
2912                   rrd_set_error("invalid color name '%s'",col_nam);
2913                 }
2914             } else {
2915                 rrd_set_error("invalid color def format");
2916                 return;
2917             }
2918             break;        
2919         case 'n':{
2920                         /* originally this used char *prop = "" and
2921                         ** char *font = "dummy" however this results
2922                         ** in a SEG fault, at least on RH7.1
2923                         **
2924                         ** The current implementation isn't proper
2925                         ** either, font is never freed and prop uses
2926                         ** a fixed width string
2927                         */
2928             char prop[100];
2929             double size = 1;
2930             char *font;
2931
2932             font=malloc(255);
2933             if(sscanf(optarg,
2934                                 "%10[A-Z]:%lf:%s",
2935                                 prop,&size,font) == 3){
2936                 int sindex;
2937                 if((sindex=text_prop_conv(prop)) != -1){
2938                     im->text_prop[sindex].size=size;              
2939                     im->text_prop[sindex].font=font;
2940                     if (sindex==0) { /* the default */
2941                         im->text_prop[TEXT_PROP_TITLE].size=size;
2942                         im->text_prop[TEXT_PROP_TITLE].font=font;
2943                         im->text_prop[TEXT_PROP_AXIS].size=size;
2944                         im->text_prop[TEXT_PROP_AXIS].font=font;
2945                         im->text_prop[TEXT_PROP_UNIT].size=size;
2946                         im->text_prop[TEXT_PROP_UNIT].font=font;
2947                         im->text_prop[TEXT_PROP_LEGEND].size=size;
2948                         im->text_prop[TEXT_PROP_LEGEND].font=font;
2949                     }
2950                 } else {
2951                     rrd_set_error("invalid fonttag '%s'",prop);
2952                     return;
2953                 }
2954             } else {
2955                 rrd_set_error("invalid text property format");
2956                 return;
2957             }
2958             break;          
2959         }
2960         case 'm':
2961             im->canvas->zoom = atof(optarg);
2962             if (im->canvas->zoom <= 0.0) {
2963                 rrd_set_error("zoom factor must be > 0");
2964                 return;
2965             }
2966           break;
2967         case 't':
2968             strncpy(im->title,optarg,150);
2969             im->title[150]='\0';
2970             break;
2971
2972         case '?':
2973             if (optopt != 0)
2974                 rrd_set_error("unknown option '%c'", optopt);
2975             else
2976                 rrd_set_error("unknown option '%s'",argv[optind-1]);
2977             return;
2978         }
2979     }
2980     
2981     if (optind >= argc) {
2982        rrd_set_error("missing filename");
2983        return;
2984     }
2985
2986     if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2987         rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");    
2988         return;
2989     }
2990
2991     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2992         /* error string is set in parsetime.c */
2993         return;
2994     }  
2995     
2996     if (start_tmp < 3600*24*365*10){
2997         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2998         return;
2999     }
3000     
3001     if (end_tmp < start_tmp) {
3002         rrd_set_error("start (%ld) should be less than end (%ld)", 
3003                start_tmp, end_tmp);
3004         return;
3005     }
3006     
3007     im->start = start_tmp;
3008     im->end = end_tmp;
3009 }
3010
3011 int
3012 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3013 {
3014     if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3015         rrd_set_error("Unknown variable '%s' in %s",varname,err);
3016         return -1;
3017     }
3018     return 0;
3019 }
3020 int
3021 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3022 {
3023     char *color;
3024     graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3025
3026     color=strstr(var,"#");
3027     if (color==NULL) {
3028         if (optional==0) {
3029             rrd_set_error("Found no color in %s",err);
3030             return 0;
3031         }
3032         return 0;
3033     } else {
3034         int n=0;
3035         char *rest;
3036         gfx_color_t    col;
3037
3038         rest=strstr(color,":");
3039         if (rest!=NULL)
3040             n=rest-color;
3041         else
3042             n=strlen(color);
3043
3044         switch (n) {
3045             case 7:
3046                 sscanf(color,"#%6lx%n",&col,&n);
3047                 col = (col << 8) + 0xff /* shift left by 8 */;
3048                 if (n!=7) rrd_set_error("Color problem in %s",err);
3049                 break;
3050             case 9:
3051                 sscanf(color,"#%8lx%n",&col,&n);
3052                 if (n==9) break;
3053             default:
3054                 rrd_set_error("Color problem in %s",err);
3055         }
3056         if (rrd_test_error()) return 0;
3057         gdp->col = col;
3058         return n;
3059     }
3060 }
3061 int
3062 rrd_graph_legend(graph_desc_t *gdp, char *line)
3063 {
3064     int i;
3065
3066     i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3067
3068     return (strlen(&line[i])==0);
3069 }
3070
3071
3072 int bad_format(char *fmt) {
3073     char *ptr;
3074     int n=0;
3075     ptr = fmt;
3076     while (*ptr != '\0')
3077         if (*ptr++ == '%') {
3078  
3079              /* line cannot end with percent char */
3080              if (*ptr == '\0') return 1;
3081  
3082              /* '%s', '%S' and '%%' are allowed */
3083              if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3084
3085              /* or else '% 6.2lf' and such are allowed */
3086              else {
3087    
3088                  /* optional padding character */
3089                  if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3090   
3091                  /* This should take care of 'm.n' with all three optional */
3092                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3093                  if (*ptr == '.') ptr++;
3094                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3095   
3096                  /* Either 'le', 'lf' or 'lg' must follow here */
3097                  if (*ptr++ != 'l') return 1;
3098                  if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3099                  else return 1;
3100                  n++;
3101             }
3102          }
3103       
3104       return (n!=1); 
3105 }
3106
3107
3108 int
3109 vdef_parse(gdes,str)
3110 struct graph_desc_t *gdes;
3111 char *str;
3112 {
3113     /* A VDEF currently is either "func" or "param,func"
3114      * so the parsing is rather simple.  Change if needed.
3115      */
3116     double      param;
3117     char        func[30];
3118     int         n;
3119     
3120     n=0;
3121     sscanf(str,"%le,%29[A-Z]%n",&param,func,&n);
3122     if (n==strlen(str)) { /* matched */
3123         ;
3124     } else {
3125         n=0;
3126         sscanf(str,"%29[A-Z]%n",func,&n);
3127         if (n==strlen(str)) { /* matched */
3128             param=DNAN;
3129         } else {
3130             rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3131                 ,str
3132                 ,gdes->vname
3133                 );
3134             return -1;
3135         }
3136     }
3137     if          (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3138     else if     (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3139     else if     (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3140     else if     (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3141     else if     (!strcmp("TOTAL",  func)) gdes->vf.op = VDEF_TOTAL;
3142     else if     (!strcmp("FIRST",  func)) gdes->vf.op = VDEF_FIRST;
3143     else if     (!strcmp("LAST",   func)) gdes->vf.op = VDEF_LAST;
3144     else {
3145         rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3146             ,func
3147             ,gdes->vname
3148             );
3149         return -1;
3150     };
3151
3152     switch (gdes->vf.op) {
3153         case VDEF_PERCENT:
3154             if (isnan(param)) { /* no parameter given */
3155                 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3156                     ,func
3157                     ,gdes->vname
3158                     );
3159                 return -1;
3160             };
3161             if (param>=0.0 && param<=100.0) {
3162                 gdes->vf.param = param;
3163                 gdes->vf.val   = DNAN;  /* undefined */
3164                 gdes->vf.when  = 0;     /* undefined */
3165             } else {
3166                 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3167                     ,param
3168                     ,gdes->vname
3169                     );
3170                 return -1;
3171             };
3172             break;
3173         case VDEF_MAXIMUM:
3174         case VDEF_AVERAGE:
3175         case VDEF_MINIMUM:
3176         case VDEF_TOTAL:
3177         case VDEF_FIRST:
3178         case VDEF_LAST:
3179             if (isnan(param)) {
3180                 gdes->vf.param = DNAN;
3181                 gdes->vf.val   = DNAN;
3182                 gdes->vf.when  = 0;
3183             } else {
3184                 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3185                     ,func
3186                     ,gdes->vname
3187                     );
3188                 return -1;
3189             };
3190             break;
3191     };
3192     return 0;
3193 }
3194
3195
3196 int
3197 vdef_calc(im,gdi)
3198 image_desc_t *im;
3199 int gdi;
3200 {
3201     graph_desc_t        *src,*dst;
3202     rrd_value_t         *data;
3203     long                step,steps;
3204
3205     dst = &im->gdes[gdi];
3206     src = &im->gdes[dst->vidx];
3207     data = src->data + src->ds;
3208     steps = (src->end - src->start) / src->step;
3209
3210 #if 0
3211 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3212     ,src->start
3213     ,src->end
3214     ,steps
3215     );
3216 #endif
3217
3218     switch (dst->vf.op) {
3219         case VDEF_PERCENT: {
3220                 rrd_value_t *   array;
3221                 int             field;
3222
3223
3224                 if ((array = malloc(steps*sizeof(double)))==NULL) {
3225                     rrd_set_error("malloc VDEV_PERCENT");
3226                     return -1;
3227                 }
3228                 for (step=0;step < steps; step++) {
3229                     array[step]=data[step*src->ds_cnt];
3230                 }
3231                 qsort(array,step,sizeof(double),vdef_percent_compar);
3232
3233                 field = (steps-1)*dst->vf.param/100;
3234                 dst->vf.val  = array[field];
3235                 dst->vf.when = 0;       /* no time component */
3236                 free(array);
3237 #if 0
3238 for(step=0;step<steps;step++)
3239 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3240 #endif
3241             }
3242             break;
3243         case VDEF_MAXIMUM:
3244             step=0;
3245             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3246             if (step == steps) {
3247                 dst->vf.val  = DNAN;
3248                 dst->vf.when = 0;
3249             } else {
3250                 dst->vf.val  = data[step*src->ds_cnt];
3251                 dst->vf.when = src->start + (step+1)*src->step;
3252             }
3253             while (step != steps) {
3254                 if (finite(data[step*src->ds_cnt])) {
3255                     if (data[step*src->ds_cnt] > dst->vf.val) {
3256                         dst->vf.val  = data[step*src->ds_cnt];
3257                         dst->vf.when = src->start + (step+1)*src->step;
3258                     }
3259                 }
3260                 step++;
3261             }
3262             break;
3263         case VDEF_TOTAL:
3264         case VDEF_AVERAGE: {
3265             int cnt=0;
3266             double sum=0.0;
3267             for (step=0;step<steps;step++) {
3268                 if (finite(data[step*src->ds_cnt])) {
3269                     sum += data[step*src->ds_cnt];
3270                     cnt ++;
3271                 };
3272             }
3273             if (cnt) {
3274                 if (dst->vf.op == VDEF_TOTAL) {
3275                     dst->vf.val  = sum*src->step;
3276                     dst->vf.when = cnt*src->step;       /* not really "when" */
3277                 } else {
3278                     dst->vf.val = sum/cnt;
3279                     dst->vf.when = 0;   /* no time component */
3280                 };
3281             } else {
3282                 dst->vf.val  = DNAN;
3283                 dst->vf.when = 0;
3284             }
3285             }
3286             break;
3287         case VDEF_MINIMUM:
3288             step=0;
3289             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3290             if (step == steps) {
3291                 dst->vf.val  = DNAN;
3292                 dst->vf.when = 0;
3293             } else {
3294                 dst->vf.val  = data[step*src->ds_cnt];
3295                 dst->vf.when = src->start + (step+1)*src->step;
3296             }
3297             while (step != steps) {
3298                 if (finite(data[step*src->ds_cnt])) {
3299                     if (data[step*src->ds_cnt] < dst->vf.val) {
3300                         dst->vf.val  = data[step*src->ds_cnt];
3301                         dst->vf.when = src->start + (step+1)*src->step;
3302                     }
3303                 }
3304                 step++;
3305             }
3306             break;
3307         case VDEF_FIRST:
3308             /* The time value returned here is one step before the
3309              * actual time value.  This is the start of the first
3310              * non-NaN interval.
3311              */
3312             step=0;
3313             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3314             if (step == steps) { /* all entries were NaN */
3315                 dst->vf.val  = DNAN;
3316                 dst->vf.when = 0;
3317             } else {
3318                 dst->vf.val  = data[step*src->ds_cnt];
3319                 dst->vf.when = src->start + step*src->step;
3320             }
3321             break;
3322         case VDEF_LAST:
3323             /* The time value returned here is the
3324              * actual time value.  This is the end of the last
3325              * non-NaN interval.
3326              */
3327             step=steps-1;
3328             while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3329             if (step < 0) { /* all entries were NaN */
3330                 dst->vf.val  = DNAN;
3331                 dst->vf.when = 0;
3332             } else {
3333                 dst->vf.val  = data[step*src->ds_cnt];
3334                 dst->vf.when = src->start + (step+1)*src->step;
3335             }
3336             break;
3337     }
3338     return 0;
3339 }
3340
3341 /* NaN < -INF < finite_values < INF */
3342 int
3343 vdef_percent_compar(a,b)
3344 const void *a,*b;
3345 {
3346     /* Equality is not returned; this doesn't hurt except
3347      * (maybe) for a little performance.
3348      */
3349
3350     /* First catch NaN values. They are smallest */
3351     if (isnan( *(double *)a )) return -1;
3352     if (isnan( *(double *)b )) return  1;
3353
3354     /* NaN doesn't reach this part so INF and -INF are extremes.
3355      * The sign from isinf() is compatible with the sign we return
3356      */
3357     if (isinf( *(double *)a )) return isinf( *(double *)a );
3358     if (isinf( *(double *)b )) return isinf( *(double *)b );
3359
3360     /* If we reach this, both values must be finite */
3361     if ( *(double *)a < *(double *)b ) return -1; else return 1;
3362 }