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