Find attached the patch I promised to send to you. Please note that there
[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 #define RRD_DEFAULT_FONT "c:/winnt/fonts/COUR.TTF"
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         leg_cc = strlen(im->gdes[i].legend);
1363         
1364         /* is there a controle code ant the end of the legend string ? */ 
1365         if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1366             prt_fctn = im->gdes[i].legend[leg_cc-1];
1367             leg_cc -= 2;
1368             im->gdes[i].legend[leg_cc] = '\0';
1369         } else {
1370             prt_fctn = '\0';
1371         }
1372         /* remove exess space */
1373         while (prt_fctn=='g' && 
1374                leg_cc > 0 && 
1375                im->gdes[i].legend[leg_cc-1]==' '){
1376            leg_cc--;
1377            im->gdes[i].legend[leg_cc]='\0';
1378         }
1379         if (leg_cc != 0 ){
1380            legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1381            
1382            if (fill > 0){ 
1383                /* no interleg space if string ends in \g */
1384                fill += legspace[i];
1385             }
1386             if (im->gdes[i].gf != GF_GPRINT && 
1387                 im->gdes[i].gf != GF_COMMENT) { 
1388                 fill += box;       
1389             }
1390            fill += gfx_get_text_width(im->canvas, fill+border,
1391                                       im->text_prop[TEXT_PROP_LEGEND].font,
1392                                       im->text_prop[TEXT_PROP_LEGEND].size,
1393                                       im->tabwidth,
1394                                       im->gdes[i].legend);
1395             leg_c++;
1396         } else {
1397            legspace[i]=0;
1398         }
1399         /* who said there was a special tag ... ?*/
1400         if (prt_fctn=='g') {    
1401            prt_fctn = '\0';
1402         }
1403         if (prt_fctn == '\0') {
1404             if (i == im->gdes_c -1 ) prt_fctn ='l';
1405             
1406             /* is it time to place the legends ? */
1407             if (fill > im->ximg - 2*border){
1408                 if (leg_c > 1) {
1409                     /* go back one */
1410                     i--; 
1411                     fill = fill_last;
1412                     leg_c--;
1413                     prt_fctn = 'j';
1414                 } else {
1415                     prt_fctn = 'l';
1416                 }
1417                 
1418             }
1419         }
1420
1421
1422         if (prt_fctn != '\0'){
1423             leg_x = border;
1424             if (leg_c >= 2 && prt_fctn == 'j') {
1425                 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1426             } else {
1427                 glue = 0;
1428             }
1429             if (prt_fctn =='c') leg_x =  (im->ximg - fill) / 2.0;
1430             if (prt_fctn =='r') leg_x =  im->ximg - fill - border;
1431
1432             for(ii=mark;ii<=i;ii++){
1433                 if(im->gdes[ii].legend[0]=='\0')
1434                     continue;
1435                 im->gdes[ii].leg_x = leg_x;
1436                 im->gdes[ii].leg_y = leg_y;
1437                 leg_x += 
1438                  gfx_get_text_width(im->canvas, leg_x,
1439                                       im->text_prop[TEXT_PROP_LEGEND].font,
1440                                       im->text_prop[TEXT_PROP_LEGEND].size,
1441                                       im->tabwidth,
1442                                       im->gdes[ii].legend) 
1443                    + legspace[ii]
1444                    + glue;
1445                 if (im->gdes[ii].gf != GF_GPRINT && 
1446                     im->gdes[ii].gf != GF_COMMENT) 
1447                     leg_x += box;          
1448             }       
1449             leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1450             if (prt_fctn == 's') leg_y -=  im->text_prop[TEXT_PROP_LEGEND].size*1.2;       
1451             fill = 0;
1452             leg_c = 0;
1453             mark = ii;
1454         }          
1455     }
1456     im->yimg = leg_y;
1457     free(legspace);
1458   }
1459   return 0;
1460 }
1461
1462 /* create a grid on the graph. it determines what to do
1463    from the values of xsize, start and end */
1464
1465 /* the xaxis labels are determined from the number of seconds per pixel
1466    in the requested graph */
1467
1468
1469
1470 int
1471 calc_horizontal_grid(image_desc_t   *im)
1472 {
1473     double   range;
1474     double   scaledrange;
1475     int      pixel,i;
1476     int      gridind;
1477     int      decimals, fractionals;
1478
1479     im->ygrid_scale.labfact=2;
1480     gridind=-1;
1481     range =  im->maxval - im->minval;
1482     scaledrange = range / im->magfact;
1483
1484         /* does the scale of this graph make it impossible to put lines
1485            on it? If so, give up. */
1486         if (isnan(scaledrange)) {
1487                 return 0;
1488         }
1489
1490     /* find grid spaceing */
1491     pixel=1;
1492     if(isnan(im->ygridstep)){
1493         if(im->extra_flags & ALTYGRID) {
1494             /* find the value with max number of digits. Get number of digits */
1495             decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1496             if(decimals <= 0) /* everything is small. make place for zero */
1497                 decimals = 1;
1498             
1499             fractionals = floor(log10(range));
1500             if(fractionals < 0) /* small amplitude. */
1501                 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1502             else
1503                 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1504             im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1505             if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1506                 im->ygrid_scale.gridstep = 0.1;
1507             /* should have at least 5 lines but no more then 15 */
1508             if(range/im->ygrid_scale.gridstep < 5)
1509                 im->ygrid_scale.gridstep /= 10;
1510             if(range/im->ygrid_scale.gridstep > 15)
1511                 im->ygrid_scale.gridstep *= 10;
1512             if(range/im->ygrid_scale.gridstep > 5) {
1513                 im->ygrid_scale.labfact = 1;
1514                 if(range/im->ygrid_scale.gridstep > 8)
1515                     im->ygrid_scale.labfact = 2;
1516             }
1517             else {
1518                 im->ygrid_scale.gridstep /= 5;
1519                 im->ygrid_scale.labfact = 5;
1520             }
1521         }
1522         else {
1523             for(i=0;ylab[i].grid > 0;i++){
1524                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1525                 if (gridind == -1 && pixel > 5) {
1526                     gridind = i;
1527                     break;
1528                 }
1529             }
1530             
1531             for(i=0; i<4;i++) {
1532                if (pixel * ylab[gridind].lfac[i] >=  2 * im->text_prop[TEXT_PROP_AXIS].size) {
1533                   im->ygrid_scale.labfact =  ylab[gridind].lfac[i];
1534                   break;
1535                }                          
1536             } 
1537             
1538             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1539         }
1540     } else {
1541         im->ygrid_scale.gridstep = im->ygridstep;
1542         im->ygrid_scale.labfact = im->ylabfact;
1543     }
1544     return 1;
1545 }
1546
1547 int draw_horizontal_grid(image_desc_t *im)
1548 {
1549     int      i;
1550     double   scaledstep;
1551     char     graph_label[100];
1552     double X0=im->xorigin;
1553     double X1=im->xorigin+im->xsize;
1554    
1555     int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1556     int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1557     scaledstep = im->ygrid_scale.gridstep/im->magfact;
1558     for (i = sgrid; i <= egrid; i++){
1559        double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1560        if ( Y0 >= im->yorigin-im->ysize
1561                  && Y0 <= im->yorigin){       
1562             if(i % im->ygrid_scale.labfact == 0){               
1563                 if (i==0 || im->symbol == ' ') {
1564                     if(scaledstep < 1){
1565                         if(im->extra_flags & ALTYGRID) {
1566                             sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1567                         }
1568                         else {
1569                             sprintf(graph_label,"%4.1f",scaledstep*i);
1570                         }
1571                     } else {
1572                         sprintf(graph_label,"%4.0f",scaledstep*i);
1573                     }
1574                 }else {
1575                     if(scaledstep < 1){
1576                         sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1577                     } else {
1578                         sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1579                     }
1580                 }
1581
1582                gfx_new_text ( im->canvas,
1583                               X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1584                               im->graph_col[GRC_FONT],
1585                               im->text_prop[TEXT_PROP_AXIS].font,
1586                               im->text_prop[TEXT_PROP_AXIS].size,
1587                               im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1588                               graph_label );
1589                gfx_new_dashed_line ( im->canvas,
1590                               X0-2,Y0,
1591                               X1+2,Y0,
1592                               MGRIDWIDTH, im->graph_col[GRC_MGRID],
1593                               im->grid_dash_on, im->grid_dash_off);            
1594                
1595             } else if (!(im->extra_flags & NOMINOR)) {          
1596                gfx_new_dashed_line ( im->canvas,
1597                               X0-1,Y0,
1598                               X1+1,Y0,
1599                               GRIDWIDTH, im->graph_col[GRC_GRID],
1600                               im->grid_dash_on, im->grid_dash_off);            
1601                
1602             }       
1603         }       
1604     } 
1605     return 1;
1606 }
1607
1608 /* logaritmic horizontal grid */
1609 int
1610 horizontal_log_grid(image_desc_t   *im)   
1611 {
1612     double   pixpex;
1613     int      ii,i;
1614     int      minoridx=0, majoridx=0;
1615     char     graph_label[100];
1616     double   X0,X1,Y0;   
1617     double   value, pixperstep, minstep;
1618
1619     /* find grid spaceing */
1620     pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1621
1622         if (isnan(pixpex)) {
1623                 return 0;
1624         }
1625
1626     for(i=0;yloglab[i][0] > 0;i++){
1627         minstep = log10(yloglab[i][0]);
1628         for(ii=1;yloglab[i][ii+1] > 0;ii++){
1629             if(yloglab[i][ii+2]==0){
1630                 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1631                 break;
1632             }
1633         }
1634         pixperstep = pixpex * minstep;
1635         if(pixperstep > 5){minoridx = i;}
1636        if(pixperstep > 2 *  im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1637     }
1638    
1639    X0=im->xorigin;
1640    X1=im->xorigin+im->xsize;
1641     /* paint minor grid */
1642     for (value = pow((double)10, log10(im->minval) 
1643                           - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1644          value  <= im->maxval;
1645          value *= yloglab[minoridx][0]){
1646         if (value < im->minval) continue;
1647         i=0;    
1648         while(yloglab[minoridx][++i] > 0){          
1649            Y0 = ytr(im,value * yloglab[minoridx][i]);
1650            if (Y0 <= im->yorigin - im->ysize) break;
1651            gfx_new_dashed_line ( im->canvas,
1652                           X0-1,Y0,
1653                           X1+1,Y0,
1654                           GRIDWIDTH, im->graph_col[GRC_GRID],
1655                           im->grid_dash_on, im->grid_dash_off);
1656         }
1657     }
1658
1659     /* paint major grid and labels*/
1660     for (value = pow((double)10, log10(im->minval) 
1661                           - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1662          value <= im->maxval;
1663          value *= yloglab[majoridx][0]){
1664         if (value < im->minval) continue;
1665         i=0;    
1666         while(yloglab[majoridx][++i] > 0){          
1667            Y0 = ytr(im,value * yloglab[majoridx][i]);    
1668            if (Y0 <= im->yorigin - im->ysize) break;
1669            gfx_new_dashed_line ( im->canvas,
1670                           X0-2,Y0,
1671                           X1+2,Y0,
1672                           MGRIDWIDTH, im->graph_col[GRC_MGRID],
1673                           im->grid_dash_on, im->grid_dash_off);
1674            
1675            sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1676            gfx_new_text ( im->canvas,
1677                           X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1678                           im->graph_col[GRC_FONT],
1679                           im->text_prop[TEXT_PROP_AXIS].font,
1680                           im->text_prop[TEXT_PROP_AXIS].size,
1681                           im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1682                           graph_label );
1683         } 
1684     }
1685         return 1;
1686 }
1687
1688
1689 void
1690 vertical_grid(
1691     image_desc_t   *im )
1692 {   
1693     int xlab_sel;               /* which sort of label and grid ? */
1694     time_t ti, tilab, timajor;
1695     long factor;
1696     char graph_label[100];
1697     double X0,Y0,Y1; /* points for filled graph and more*/
1698     struct tm tm;
1699
1700     /* the type of time grid is determined by finding
1701        the number of seconds per pixel in the graph */
1702     
1703     
1704     if(im->xlab_user.minsec == -1){
1705         factor=(im->end - im->start)/im->xsize;
1706         xlab_sel=0;
1707         while ( xlab[xlab_sel+1].minsec != -1 
1708                 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1709         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1710         im->xlab_user.gridst = xlab[xlab_sel].gridst;
1711         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1712         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1713         im->xlab_user.labtm = xlab[xlab_sel].labtm;
1714         im->xlab_user.labst = xlab[xlab_sel].labst;
1715         im->xlab_user.precis = xlab[xlab_sel].precis;
1716         im->xlab_user.stst = xlab[xlab_sel].stst;
1717     }
1718     
1719     /* y coords are the same for every line ... */
1720     Y0 = im->yorigin;
1721     Y1 = im->yorigin-im->ysize;
1722    
1723
1724     /* paint the minor grid */
1725     if (!(im->extra_flags & NOMINOR))
1726     {
1727         for(ti = find_first_time(im->start,
1728                                 im->xlab_user.gridtm,
1729                                 im->xlab_user.gridst),
1730             timajor = find_first_time(im->start,
1731                                 im->xlab_user.mgridtm,
1732                                 im->xlab_user.mgridst);
1733             ti < im->end; 
1734             ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1735             ){
1736             /* are we inside the graph ? */
1737             if (ti < im->start || ti > im->end) continue;
1738             while (timajor < ti) {
1739                 timajor = find_next_time(timajor,
1740                         im->xlab_user.mgridtm, im->xlab_user.mgridst);
1741             }
1742             if (ti == timajor) continue; /* skip as falls on major grid line */
1743            X0 = xtr(im,ti);       
1744            gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1745                im->graph_col[GRC_GRID],
1746                im->grid_dash_on, im->grid_dash_off);
1747            
1748         }
1749     }
1750
1751     /* paint the major grid */
1752     for(ti = find_first_time(im->start,
1753                             im->xlab_user.mgridtm,
1754                             im->xlab_user.mgridst);
1755         ti < im->end; 
1756         ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1757         ){
1758         /* are we inside the graph ? */
1759         if (ti < im->start || ti > im->end) continue;
1760        X0 = xtr(im,ti);
1761        gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1762            im->graph_col[GRC_MGRID],
1763            im->grid_dash_on, im->grid_dash_off);
1764        
1765     }
1766     /* paint the labels below the graph */
1767     for(ti = find_first_time(im->start,
1768                             im->xlab_user.labtm,
1769                             im->xlab_user.labst);
1770         ti <= im->end; 
1771         ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1772         ){
1773         tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1774         /* are we inside the graph ? */
1775         if (ti < im->start || ti > im->end) continue;
1776
1777 #if HAVE_STRFTIME
1778         localtime_r(&tilab, &tm);
1779         strftime(graph_label,99,im->xlab_user.stst, &tm);
1780 #else
1781 # error "your libc has no strftime I guess we'll abort the exercise here."
1782 #endif
1783        gfx_new_text ( im->canvas,
1784                       xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1785                       im->graph_col[GRC_FONT],
1786                       im->text_prop[TEXT_PROP_AXIS].font,
1787                       im->text_prop[TEXT_PROP_AXIS].size,
1788                       im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1789                       graph_label );
1790        
1791     }
1792
1793 }
1794
1795
1796 void 
1797 axis_paint(
1798    image_desc_t   *im
1799            )
1800 {   
1801     /* draw x and y axis */
1802     gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1803                       im->xorigin+im->xsize,im->yorigin-im->ysize,
1804                       GRIDWIDTH, im->graph_col[GRC_GRID]);
1805        
1806        gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1807                          im->xorigin+im->xsize,im->yorigin-im->ysize,
1808                          GRIDWIDTH, im->graph_col[GRC_GRID]);
1809    
1810        gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1811                          im->xorigin+im->xsize+4,im->yorigin,
1812                          MGRIDWIDTH, im->graph_col[GRC_GRID]);
1813    
1814        gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1815                          im->xorigin,im->yorigin-im->ysize-4,
1816                          MGRIDWIDTH, im->graph_col[GRC_GRID]);
1817    
1818     
1819     /* arrow for X axis direction */
1820     gfx_new_area ( im->canvas, 
1821                    im->xorigin+im->xsize+3,  im->yorigin-3,
1822                    im->xorigin+im->xsize+3,  im->yorigin+4,
1823                    im->xorigin+im->xsize+8,  im->yorigin+0.5, /* LINEOFFSET */
1824                    im->graph_col[GRC_ARROW]);
1825    
1826    
1827    
1828 }
1829
1830 void
1831 grid_paint(image_desc_t   *im)
1832 {   
1833     long i;
1834     int res=0;
1835     double X0,Y0; /* points for filled graph and more*/
1836     gfx_node_t *node;
1837
1838     /* draw 3d border */
1839     node = gfx_new_area (im->canvas, 0,im->yimg,
1840                                  2,im->yimg-2,
1841                                  2,2,im->graph_col[GRC_SHADEA]);
1842     gfx_add_point( node , im->ximg - 2, 2 );
1843     gfx_add_point( node , im->ximg, 0 );
1844     gfx_add_point( node , 0,0 );
1845 /*    gfx_add_point( node , 0,im->yimg ); */
1846    
1847     node =  gfx_new_area (im->canvas, 2,im->yimg-2,
1848                                   im->ximg-2,im->yimg-2,
1849                                   im->ximg - 2, 2,
1850                                  im->graph_col[GRC_SHADEB]);
1851     gfx_add_point( node ,   im->ximg,0);
1852     gfx_add_point( node ,   im->ximg,im->yimg);
1853     gfx_add_point( node ,   0,im->yimg);
1854 /*    gfx_add_point( node , 0,im->yimg ); */
1855    
1856    
1857     if (im->draw_x_grid == 1 )
1858       vertical_grid(im);
1859     
1860     if (im->draw_y_grid == 1){
1861         if(im->logarithmic){
1862                 res = horizontal_log_grid(im);
1863         } else {
1864                 res = draw_horizontal_grid(im);
1865         }
1866
1867         /* dont draw horizontal grid if there is no min and max val */
1868         if (! res ) {
1869           char *nodata = "No Data found";
1870            gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1871                         im->graph_col[GRC_FONT],
1872                         im->text_prop[TEXT_PROP_AXIS].font,
1873                         im->text_prop[TEXT_PROP_AXIS].size,
1874                         im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1875                         nodata );          
1876         }
1877     }
1878
1879     /* yaxis description */
1880         if (im->canvas->imgformat != IF_PNG) {
1881             gfx_new_text( im->canvas,
1882                           7, (im->yorigin - im->ysize/2),
1883                           im->graph_col[GRC_FONT],
1884                           im->text_prop[TEXT_PROP_AXIS].font,
1885                           im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1886                           GFX_H_CENTER, GFX_V_CENTER,
1887                           im->ylegend);
1888         } else {
1889             /* horrible hack until we can actually print vertically */
1890             {
1891                 int n;
1892                 int l=strlen(im->ylegend);
1893                 char s[2];
1894                 for (n=0;n<strlen(im->ylegend);n++) {
1895                     s[0]=im->ylegend[n];
1896                     s[1]='\0';
1897                     gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1898                         im->graph_col[GRC_FONT],
1899                         im->text_prop[TEXT_PROP_AXIS].font,
1900                         im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1901                         GFX_H_CENTER, GFX_V_CENTER,
1902                         s);
1903                 }
1904             }
1905         }
1906    
1907     /* graph title */
1908     gfx_new_text( im->canvas,
1909                   im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1910                   im->graph_col[GRC_FONT],
1911                   im->text_prop[TEXT_PROP_TITLE].font,
1912                   im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1913                   GFX_H_CENTER, GFX_V_CENTER,
1914                   im->title);
1915
1916     /* graph labels */
1917     if( !(im->extra_flags & NOLEGEND) ) {
1918       for(i=0;i<im->gdes_c;i++){
1919         if(im->gdes[i].legend[0] =='\0')
1920             continue;
1921          
1922         /* im->gdes[i].leg_y is the bottom of the legend */
1923                 X0 = im->gdes[i].leg_x;
1924                 Y0 = im->gdes[i].leg_y;
1925                 /* Box needed? */
1926                 if (       im->gdes[i].gf != GF_GPRINT
1927                         && im->gdes[i].gf != GF_COMMENT) {
1928                     int boxH, boxV;
1929
1930                     boxH = gfx_get_text_width(im->canvas, 0,
1931                                 im->text_prop[TEXT_PROP_AXIS].font,
1932                                 im->text_prop[TEXT_PROP_AXIS].size,
1933                                 im->tabwidth,"M") * 1.25;
1934                     boxV = boxH;
1935
1936                     node = gfx_new_area(im->canvas,
1937                                 X0,Y0-boxV,
1938                                 X0,Y0,
1939                                 X0+boxH,Y0,
1940                                 im->gdes[i].col);
1941                     gfx_add_point ( node, X0+boxH, Y0-boxV );
1942                     node = gfx_new_line(im->canvas,
1943                                 X0,Y0-boxV, X0,Y0,
1944                                 1,0x000000FF);
1945                     gfx_add_point(node,X0+boxH,Y0);
1946                     gfx_add_point(node,X0+boxH,Y0-boxV);
1947                     gfx_close_path(node);
1948                     X0 += boxH / 1.25 * 2;
1949                 }
1950                 gfx_new_text ( im->canvas, X0, Y0,
1951                                    im->graph_col[GRC_FONT],
1952                                    im->text_prop[TEXT_PROP_AXIS].font,
1953                                    im->text_prop[TEXT_PROP_AXIS].size,
1954                                    im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1955                                    im->gdes[i].legend );
1956               }
1957            }
1958         }
1959
1960
1961 /*****************************************************
1962  * lazy check make sure we rely need to create this graph
1963  *****************************************************/
1964
1965 int lazy_check(image_desc_t *im){
1966     FILE *fd = NULL;
1967         int size = 1;
1968     struct stat  imgstat;
1969     
1970     if (im->lazy == 0) return 0; /* no lazy option */
1971     if (stat(im->graphfile,&imgstat) != 0) 
1972       return 0; /* can't stat */
1973     /* one pixel in the existing graph is more then what we would
1974        change here ... */
1975     if (time(NULL) - imgstat.st_mtime > 
1976         (im->end - im->start) / im->xsize) 
1977       return 0;
1978     if ((fd = fopen(im->graphfile,"rb")) == NULL) 
1979       return 0; /* the file does not exist */
1980     switch (im->canvas->imgformat) {
1981     case IF_PNG:
1982            size = PngSize(fd,&(im->ximg),&(im->yimg));
1983            break;
1984     default:
1985            size = 1;
1986     }
1987     fclose(fd);
1988     return size;
1989 }
1990
1991 void
1992 pie_part(image_desc_t *im, gfx_color_t color,
1993             double PieCenterX, double PieCenterY, double Radius,
1994             double startangle, double endangle)
1995 {
1996     gfx_node_t *node;
1997     double angle;
1998     double step=M_PI/50; /* Number of iterations for the circle;
1999                          ** 10 is definitely too low, more than
2000                          ** 50 seems to be overkill
2001                          */
2002
2003     /* Strange but true: we have to work clockwise or else
2004     ** anti aliasing nor transparency don't work.
2005     **
2006     ** This test is here to make sure we do it right, also
2007     ** this makes the for...next loop more easy to implement.
2008     ** The return will occur if the user enters a negative number
2009     ** (which shouldn't be done according to the specs) or if the
2010     ** programmers do something wrong (which, as we all know, never
2011     ** happens anyway :)
2012     */
2013     if (endangle<startangle) return;
2014
2015     /* Hidden feature: Radius decreases each full circle */
2016     angle=startangle;
2017     while (angle>=2*M_PI) {
2018         angle  -= 2*M_PI;
2019         Radius *= 0.8;
2020     }
2021
2022     node=gfx_new_area(im->canvas,
2023                 PieCenterX+sin(startangle)*Radius,
2024                 PieCenterY-cos(startangle)*Radius,
2025                 PieCenterX,
2026                 PieCenterY,
2027                 PieCenterX+sin(endangle)*Radius,
2028                 PieCenterY-cos(endangle)*Radius,
2029                 color);
2030     for (angle=endangle;angle-startangle>=step;angle-=step) {
2031         gfx_add_point(node,
2032                 PieCenterX+sin(angle)*Radius,
2033                 PieCenterY-cos(angle)*Radius );
2034     }
2035 }
2036
2037 int
2038 graph_size_location(image_desc_t *im, int elements, int piechart )
2039 {
2040     /* The actual size of the image to draw is determined from
2041     ** several sources.  The size given on the command line is
2042     ** the graph area but we need more as we have to draw labels
2043     ** and other things outside the graph area
2044     */
2045
2046     /* +-+-------------------------------------------+
2047     ** |l|.................title.....................|
2048     ** |e+--+-------------------------------+--------+
2049     ** |b| b|                               |        |
2050     ** |a| a|                               |  pie   |
2051     ** |l| l|          main graph area      | chart  |
2052     ** |.| .|                               |  area  |
2053     ** |t| y|                               |        |
2054     ** |r+--+-------------------------------+--------+
2055     ** |e|  | x-axis labels                 |        |
2056     ** |v+--+-------------------------------+--------+
2057     ** | |..............legends......................|
2058     ** +-+-------------------------------------------+
2059     */
2060     int Xvertical=0,    Yvertical=0,
2061         Xtitle   =0,    Ytitle   =0,
2062         Xylabel  =0,    Yylabel  =0,
2063         Xmain    =0,    Ymain    =0,
2064         Xpie     =0,    Ypie     =0,
2065         Xxlabel  =0,    Yxlabel  =0,
2066 #if 0
2067         Xlegend  =0,    Ylegend  =0,
2068 #endif
2069         Xspacing =10,   Yspacing =10;
2070
2071     if (im->ylegend[0] != '\0') {
2072         Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2073         Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2074     }
2075
2076     if (im->title[0] != '\0') {
2077         /* The title is placed "inbetween" two text lines so it
2078         ** automatically has some vertical spacing.  The horizontal
2079         ** spacing is added here, on each side.
2080         */
2081         Xtitle = gfx_get_text_width(im->canvas, 0,
2082                 im->text_prop[TEXT_PROP_TITLE].font,
2083                 im->text_prop[TEXT_PROP_TITLE].size,
2084                 im->tabwidth,
2085                 im->title) + 2*Xspacing;
2086         Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2087     }
2088
2089     if (elements) {
2090         Xmain=im->xsize;
2091         Ymain=im->ysize;
2092         if (im->draw_x_grid) {
2093             Xxlabel=Xmain;
2094             Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2095         }
2096         if (im->draw_y_grid) {
2097             Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2098             Yylabel=Ymain;
2099         }
2100     }
2101
2102     if (piechart) {
2103         im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2104         Xpie=im->piesize;
2105         Ypie=im->piesize;
2106     }
2107
2108     /* Now calculate the total size.  Insert some spacing where
2109        desired.  im->xorigin and im->yorigin need to correspond
2110        with the lower left corner of the main graph area or, if
2111        this one is not set, the imaginary box surrounding the
2112        pie chart area. */
2113
2114     /* The legend width cannot yet be determined, as a result we
2115     ** have problems adjusting the image to it.  For now, we just
2116     ** forget about it at all; the legend will have to fit in the
2117     ** size already allocated.
2118     */
2119     im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2120     if (Xmain) im->ximg += Xspacing;
2121     if (Xpie) im->ximg += Xspacing;
2122     im->xorigin = Xspacing + Xylabel;
2123     if (Xtitle > im->ximg) im->ximg = Xtitle;
2124     if (Xvertical) {
2125         im->ximg += Xvertical;
2126         im->xorigin += Xvertical;
2127     }
2128     xtr(im,0);
2129
2130     /* The vertical size is interesting... we need to compare
2131     ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2132     ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2133     ** start even thinking about Ylegend.
2134     **
2135     ** Do it in three portions: First calculate the inner part,
2136     ** then do the legend, then adjust the total height of the img.
2137     */
2138
2139     /* reserve space for main and/or pie */
2140     im->yimg = Ymain + Yxlabel;
2141     if (im->yimg < Ypie) im->yimg = Ypie;
2142     im->yorigin = im->yimg - Yxlabel;
2143     /* reserve space for the title *or* some padding above the graph */
2144     if (Ytitle) {
2145         im->yimg += Ytitle;
2146         im->yorigin += Ytitle;
2147     } else {
2148         im->yimg += Yspacing;
2149         im->yorigin += Yspacing;
2150     }
2151     /* reserve space for padding below the graph */
2152     im->yimg += Yspacing;
2153     ytr(im,DNAN);
2154
2155     /* Determine where to place the legends onto the image.
2156     ** Adjust im->yimg to match the space requirements.
2157     */
2158     if(leg_place(im)==-1)
2159         return -1;
2160
2161     /* last of three steps: check total height of image */
2162     if (im->yimg < Yvertical) im->yimg = Yvertical;
2163
2164 #if 0
2165     if (Xlegend > im->ximg) {
2166         im->ximg = Xlegend;
2167         /* reposition Pie */
2168     }
2169 #endif
2170
2171     /* The pie is placed in the upper right hand corner,
2172     ** just below the title (if any) and with sufficient
2173     ** padding.
2174     */
2175     if (elements) {
2176         im->pie_x = im->ximg - Xspacing - Xpie/2;
2177         im->pie_y = im->yorigin-Ymain+Ypie/2;
2178     } else {
2179         im->pie_x = im->ximg/2;
2180         im->pie_y = im->yorigin-Ypie/2;
2181     }
2182
2183     return 0;
2184 }
2185
2186 /* draw that picture thing ... */
2187 int
2188 graph_paint(image_desc_t *im, char ***calcpr)
2189 {
2190   int i,ii;
2191   int lazy =     lazy_check(im);
2192   int piechart = 0;
2193   double PieStart=0.0;
2194   FILE  *fo;
2195   gfx_node_t *node;
2196   
2197   double areazero = 0.0;
2198   enum gf_en stack_gf = GF_PRINT;
2199   graph_desc_t *lastgdes = NULL;    
2200
2201   /* if we are lazy and there is nothing to PRINT ... quit now */
2202   if (lazy && im->prt_c==0) return 0;
2203
2204   /* pull the data from the rrd files ... */
2205   
2206   if(data_fetch(im)==-1)
2207     return -1;
2208
2209   /* evaluate VDEF and CDEF operations ... */
2210   if(data_calc(im)==-1)
2211     return -1;
2212   
2213   /* check if we need to draw a piechart */
2214   for(i=0;i<im->gdes_c;i++){
2215     if (im->gdes[i].gf == GF_PART) {
2216       piechart=1;
2217       break;
2218     }
2219   }
2220
2221   /* calculate and PRINT and GPRINT definitions. We have to do it at
2222    * this point because it will affect the length of the legends
2223    * if there are no graph elements we stop here ... 
2224    * if we are lazy, try to quit ... 
2225    */
2226   i=print_calc(im,calcpr);
2227   if(i<0) return -1;
2228   if(((i==0)&&(piechart==0)) || lazy) return 0;
2229
2230   /* If there's only the pie chart to draw, signal this */
2231   if (i==0) piechart=2;
2232   
2233   /* get actual drawing data and find min and max values*/
2234   if(data_proc(im)==-1)
2235     return -1;
2236   
2237   if(!im->logarithmic){si_unit(im);}        /* identify si magnitude Kilo, Mega Giga ? */
2238   
2239   if(!im->rigid && ! im->logarithmic)
2240     expand_range(im);   /* make sure the upper and lower limit are
2241                            sensible values */
2242
2243   if (!calc_horizontal_grid(im))
2244     return -1;
2245   if (im->gridfit)
2246     apply_gridfit(im);
2247
2248 /**************************************************************
2249  *** Calculating sizes and locations became a bit confusing ***
2250  *** so I moved this into a separate function.              ***
2251  **************************************************************/
2252   if(graph_size_location(im,i,piechart)==-1)
2253     return -1;
2254
2255   /* the actual graph is created by going through the individual
2256      graph elements and then drawing them */
2257   
2258   node=gfx_new_area ( im->canvas,
2259                       0, 0,
2260                       im->ximg, 0,
2261                       im->ximg, im->yimg,
2262                       im->graph_col[GRC_BACK]);
2263
2264   gfx_add_point(node,0, im->yimg);
2265
2266   if (piechart != 2) {
2267     node=gfx_new_area ( im->canvas,
2268                       im->xorigin,             im->yorigin, 
2269                       im->xorigin + im->xsize, im->yorigin,
2270                       im->xorigin + im->xsize, im->yorigin-im->ysize,
2271                       im->graph_col[GRC_CANVAS]);
2272   
2273     gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2274
2275     if (im->minval > 0.0)
2276       areazero = im->minval;
2277     if (im->maxval < 0.0)
2278       areazero = im->maxval;
2279   
2280     axis_paint(im);
2281   }
2282
2283   if (piechart) {
2284     pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2285   }
2286
2287   for(i=0;i<im->gdes_c;i++){
2288     switch(im->gdes[i].gf){
2289     case GF_CDEF:
2290     case GF_VDEF:
2291     case GF_DEF:
2292     case GF_PRINT:
2293     case GF_GPRINT:
2294     case GF_COMMENT:
2295     case GF_HRULE:
2296     case GF_VRULE:
2297     case GF_XPORT:
2298       break;
2299     case GF_TICK:
2300       for (ii = 0; ii < im->xsize; ii++)
2301         {
2302           if (!isnan(im->gdes[i].p_data[ii]) && 
2303               im->gdes[i].p_data[ii] > 0.0)
2304             { 
2305               /* generate a tick */
2306               gfx_new_line(im->canvas, im -> xorigin + ii, 
2307                            im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2308                            im -> xorigin + ii, 
2309                            im -> yorigin,
2310                            1.0,
2311                            im -> gdes[i].col );
2312             }
2313         }
2314       break;
2315     case GF_LINE:
2316     case GF_AREA:
2317       stack_gf = im->gdes[i].gf;
2318     case GF_STACK:          
2319       /* fix data points at oo and -oo */
2320       for(ii=0;ii<im->xsize;ii++){
2321         if (isinf(im->gdes[i].p_data[ii])){
2322           if (im->gdes[i].p_data[ii] > 0) {
2323             im->gdes[i].p_data[ii] = im->maxval ;
2324           } else {
2325             im->gdes[i].p_data[ii] = im->minval ;
2326           }                 
2327           
2328         }
2329       } /* for */
2330       
2331       if (im->gdes[i].col != 0x0){               
2332         /* GF_LINE and friend */
2333         if(stack_gf == GF_LINE ){
2334           node = NULL;
2335           for(ii=1;ii<im->xsize;ii++){
2336             if ( ! isnan(im->gdes[i].p_data[ii-1])
2337                  && ! isnan(im->gdes[i].p_data[ii])){
2338               if (node == NULL){
2339                 node = gfx_new_line(im->canvas,
2340                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2341                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2342                                     im->gdes[i].linewidth,
2343                                     im->gdes[i].col);
2344               } else {
2345                 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2346               }
2347             } else {
2348               node = NULL;
2349             }
2350           }
2351         } else {
2352           int area_start=-1;
2353           node = NULL;
2354           for(ii=1;ii<im->xsize;ii++){
2355             /* open an area */
2356             if ( ! isnan(im->gdes[i].p_data[ii-1])
2357                  && ! isnan(im->gdes[i].p_data[ii])){
2358               if (node == NULL){
2359                 float ybase = 0.0;
2360 /*
2361                 if (im->gdes[i].gf == GF_STACK) {
2362 */
2363                 if ( (im->gdes[i].gf == GF_STACK)
2364                   || (im->gdes[i].stack) ) {
2365
2366                   ybase = ytr(im,lastgdes->p_data[ii-1]);
2367                 } else {
2368                   ybase =  ytr(im,areazero);
2369                 }
2370                 area_start = ii-1;
2371                 node = gfx_new_area(im->canvas,
2372                                     ii-1+im->xorigin,ybase,
2373                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2374                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2375                                     im->gdes[i].col
2376                                     );
2377               } else {
2378                 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2379               }
2380             }
2381
2382             if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2383               /* GF_AREA STACK type*/
2384 /*
2385               if (im->gdes[i].gf == GF_STACK ) {
2386 */
2387               if ( (im->gdes[i].gf == GF_STACK)
2388                 || (im->gdes[i].stack) ) {
2389                 int iii;
2390                 for (iii=ii-1;iii>area_start;iii--){
2391                   gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2392                 }
2393               } else {
2394                 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2395               };
2396               node=NULL;
2397             };
2398           }             
2399         } /* else GF_LINE */
2400       } /* if color != 0x0 */
2401       /* make sure we do not run into trouble when stacking on NaN */
2402       for(ii=0;ii<im->xsize;ii++){
2403         if (isnan(im->gdes[i].p_data[ii])) {
2404           if (lastgdes && (im->gdes[i].gf == GF_STACK)) {
2405             im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2406           } else {
2407             im->gdes[i].p_data[ii] =  ytr(im,areazero);
2408           }
2409         }
2410       } 
2411       lastgdes = &(im->gdes[i]);                         
2412       break;
2413     case GF_PART:
2414       if(isnan(im->gdes[i].yrule)) /* fetch variable */
2415         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2416      
2417       if (finite(im->gdes[i].yrule)) {  /* even the fetched var can be NaN */
2418         pie_part(im,im->gdes[i].col,
2419                 im->pie_x,im->pie_y,im->piesize*0.4,
2420                 M_PI*2.0*PieStart/100.0,
2421                 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2422         PieStart += im->gdes[i].yrule;
2423       }
2424       break;
2425     } /* switch */
2426   }
2427   if (piechart==2) {
2428     im->draw_x_grid=0;
2429     im->draw_y_grid=0;
2430   }
2431   /* grid_paint also does the text */
2432   grid_paint(im);
2433   
2434   /* the RULES are the last thing to paint ... */
2435   for(i=0;i<im->gdes_c;i++){    
2436     
2437     switch(im->gdes[i].gf){
2438     case GF_HRULE:
2439       if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2440         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2441       };
2442       if(im->gdes[i].yrule >= im->minval
2443          && im->gdes[i].yrule <= im->maxval)
2444         gfx_new_line(im->canvas,
2445                      im->xorigin,ytr(im,im->gdes[i].yrule),
2446                      im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2447                      1.0,im->gdes[i].col); 
2448       break;
2449     case GF_VRULE:
2450       if(im->gdes[i].xrule == 0) { /* fetch variable */
2451         im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2452       };
2453       if(im->gdes[i].xrule >= im->start
2454          && im->gdes[i].xrule <= im->end)
2455         gfx_new_line(im->canvas,
2456                      xtr(im,im->gdes[i].xrule),im->yorigin,
2457                      xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2458                      1.0,im->gdes[i].col); 
2459       break;
2460     default:
2461       break;
2462     }
2463   }
2464
2465   
2466   if (strcmp(im->graphfile,"-")==0) {
2467 #ifdef WIN32
2468     /* Change translation mode for stdout to BINARY */
2469     _setmode( _fileno( stdout ), O_BINARY );
2470 #endif
2471     fo = stdout;
2472   } else {
2473     if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2474       rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2475                     rrd_strerror(errno));
2476       return (-1);
2477     }
2478   }
2479   gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2480   if (strcmp(im->graphfile,"-") != 0)
2481     fclose(fo);
2482   return 0;
2483 }
2484
2485
2486 /*****************************************************
2487  * graph stuff 
2488  *****************************************************/
2489
2490 int
2491 gdes_alloc(image_desc_t *im){
2492
2493     unsigned long def_step = (im->end-im->start)/im->xsize;
2494     
2495     if (im->step > def_step) /* step can be increassed ... no decreassed */
2496       def_step = im->step;
2497
2498     im->gdes_c++;
2499     
2500     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2501                                            * sizeof(graph_desc_t)))==NULL){
2502         rrd_set_error("realloc graph_descs");
2503         return -1;
2504     }
2505
2506
2507     im->gdes[im->gdes_c-1].step=def_step; 
2508     im->gdes[im->gdes_c-1].stack=0;
2509     im->gdes[im->gdes_c-1].debug=0;
2510     im->gdes[im->gdes_c-1].start=im->start; 
2511     im->gdes[im->gdes_c-1].end=im->end; 
2512     im->gdes[im->gdes_c-1].vname[0]='\0'; 
2513     im->gdes[im->gdes_c-1].data=NULL;
2514     im->gdes[im->gdes_c-1].ds_namv=NULL;
2515     im->gdes[im->gdes_c-1].data_first=0;
2516     im->gdes[im->gdes_c-1].p_data=NULL;
2517     im->gdes[im->gdes_c-1].rpnp=NULL;
2518     im->gdes[im->gdes_c-1].col = 0x0;
2519     im->gdes[im->gdes_c-1].legend[0]='\0';
2520     im->gdes[im->gdes_c-1].rrd[0]='\0';
2521     im->gdes[im->gdes_c-1].ds=-1;    
2522     im->gdes[im->gdes_c-1].p_data=NULL;    
2523     im->gdes[im->gdes_c-1].yrule=DNAN;
2524     im->gdes[im->gdes_c-1].xrule=0;
2525     return 0;
2526 }
2527
2528 /* copies input untill the first unescaped colon is found
2529    or until input ends. backslashes have to be escaped as well */
2530 int
2531 scan_for_col(char *input, int len, char *output)
2532 {
2533     int inp,outp=0;
2534     for (inp=0; 
2535          inp < len &&
2536            input[inp] != ':' &&
2537            input[inp] != '\0';
2538          inp++){
2539       if (input[inp] == '\\' &&
2540           input[inp+1] != '\0' && 
2541           (input[inp+1] == '\\' ||
2542            input[inp+1] == ':')){
2543         output[outp++] = input[++inp];
2544       }
2545       else {
2546         output[outp++] = input[inp];
2547       }
2548     }
2549     output[outp] = '\0';
2550     return inp;
2551 }
2552 /* Some surgery done on this function, it became ridiculously big.
2553 ** Things moved:
2554 ** - initializing     now in rrd_graph_init()
2555 ** - options parsing  now in rrd_graph_options()
2556 ** - script parsing   now in rrd_graph_script()
2557 */
2558 int 
2559 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2560 {
2561     image_desc_t   im;
2562             
2563     rrd_graph_init(&im);
2564
2565     rrd_graph_options(argc,argv,&im);
2566     if (rrd_test_error()) {
2567         im_free(&im);
2568         return -1;
2569     }
2570     
2571     if (strlen(argv[optind])>=MAXPATH) {
2572         rrd_set_error("filename (including path) too long");
2573         im_free(&im);
2574         return -1;
2575     }
2576     strncpy(im.graphfile,argv[optind],MAXPATH-1);
2577     im.graphfile[MAXPATH-1]='\0';
2578
2579     rrd_graph_script(argc,argv,&im);
2580     if (rrd_test_error()) {
2581         im_free(&im);
2582         return -1;
2583     }
2584
2585     /* Everything is now read and the actual work can start */
2586
2587     (*prdata)=NULL;
2588     if (graph_paint(&im,prdata)==-1){
2589         im_free(&im);
2590         return -1;
2591     }
2592
2593     /* The image is generated and needs to be output.
2594     ** Also, if needed, print a line with information about the image.
2595     */
2596
2597     *xsize=im.ximg;
2598     *ysize=im.yimg;
2599     if (im.imginfo) {
2600         char *filename;
2601         if (!(*prdata)) {
2602             /* maybe prdata is not allocated yet ... lets do it now */
2603             if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2604                 rrd_set_error("malloc imginfo");
2605                 return -1; 
2606             };
2607         }
2608         if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2609          ==NULL){
2610             rrd_set_error("malloc imginfo");
2611             return -1;
2612         }
2613         filename=im.graphfile+strlen(im.graphfile);
2614         while(filename > im.graphfile) {
2615             if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2616             filename--;
2617         }
2618
2619         sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2620     }
2621     im_free(&im);
2622     return 0;
2623 }
2624
2625 void
2626 rrd_graph_init(image_desc_t *im)
2627 {
2628     unsigned int i;
2629
2630 #ifdef HAVE_TZSET
2631     tzset();
2632 #endif
2633 #ifdef HAVE_SETLOCALE
2634     setlocale(LC_TIME,"");
2635 #endif
2636
2637     im->xlab_user.minsec = -1;
2638     im->ximg=0;
2639     im->yimg=0;
2640     im->xsize = 400;
2641     im->ysize = 100;
2642     im->step = 0;
2643     im->ylegend[0] = '\0';
2644     im->title[0] = '\0';
2645     im->minval = DNAN;
2646     im->maxval = DNAN;    
2647     im->unitsexponent= 9999;
2648     im->extra_flags= 0;
2649     im->rigid = 0;
2650     im->gridfit = 1;
2651     im->imginfo = NULL;
2652     im->lazy = 0;
2653     im->logarithmic = 0;
2654     im->ygridstep = DNAN;
2655     im->draw_x_grid = 1;
2656     im->draw_y_grid = 1;
2657     im->base = 1000;
2658     im->prt_c = 0;
2659     im->gdes_c = 0;
2660     im->gdes = NULL;
2661     im->canvas = gfx_new_canvas();
2662     im->grid_dash_on = 1;
2663     im->grid_dash_off = 1;
2664
2665     for(i=0;i<DIM(graph_col);i++)
2666         im->graph_col[i]=graph_col[i];
2667
2668     for(i=0;i<DIM(text_prop);i++){        
2669       im->text_prop[i].size = text_prop[i].size;
2670       im->text_prop[i].font = text_prop[i].font;
2671     }
2672 }
2673
2674 void
2675 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2676 {
2677     int                 stroff;    
2678     char                *parsetime_error = NULL;
2679     char                scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2680     time_t              start_tmp=0,end_tmp=0;
2681     long                long_tmp;
2682     struct time_value   start_tv, end_tv;
2683     gfx_color_t         color;
2684
2685     parsetime("end-24h", &start_tv);
2686     parsetime("now", &end_tv);
2687
2688     while (1){
2689         static struct option long_options[] =
2690         {
2691             {"start",      required_argument, 0,  's'},
2692             {"end",        required_argument, 0,  'e'},
2693             {"x-grid",     required_argument, 0,  'x'},
2694             {"y-grid",     required_argument, 0,  'y'},
2695             {"vertical-label",required_argument,0,'v'},
2696             {"width",      required_argument, 0,  'w'},
2697             {"height",     required_argument, 0,  'h'},
2698             {"interlaced", no_argument,       0,  'i'},
2699             {"upper-limit",required_argument, 0,  'u'},
2700             {"lower-limit",required_argument, 0,  'l'},
2701             {"rigid",      no_argument,       0,  'r'},
2702             {"base",       required_argument, 0,  'b'},
2703             {"logarithmic",no_argument,       0,  'o'},
2704             {"color",      required_argument, 0,  'c'},
2705             {"font",       required_argument, 0,  'n'},
2706             {"title",      required_argument, 0,  't'},
2707             {"imginfo",    required_argument, 0,  'f'},
2708             {"imgformat",  required_argument, 0,  'a'},
2709             {"lazy",       no_argument,       0,  'z'},
2710             {"zoom",       required_argument, 0,  'm'},
2711             {"no-legend",  no_argument,       0,  'g'},
2712             {"alt-y-grid", no_argument,       0,  'Y'},
2713             {"no-minor",   no_argument,       0,  'I'},
2714             {"alt-autoscale", no_argument,    0,  'A'},
2715             {"alt-autoscale-max", no_argument, 0, 'M'},
2716             {"units-exponent",required_argument, 0, 'X'},
2717             {"step",       required_argument, 0,    'S'},
2718             {"no-gridfit", no_argument,       0,   'N'},
2719             {0,0,0,0}};
2720         int option_index = 0;
2721         int opt;
2722
2723
2724         opt = getopt_long(argc, argv, 
2725                           "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgYAMX:S:N",
2726                           long_options, &option_index);
2727
2728         if (opt == EOF)
2729             break;
2730         
2731         switch(opt) {
2732         case 'I':
2733             im->extra_flags |= NOMINOR;
2734             break;
2735         case 'Y':
2736             im->extra_flags |= ALTYGRID;
2737             break;
2738         case 'A':
2739             im->extra_flags |= ALTAUTOSCALE;
2740             break;
2741         case 'M':
2742             im->extra_flags |= ALTAUTOSCALE_MAX;
2743             break;
2744         case 'g':
2745             im->extra_flags |= NOLEGEND;
2746             break;
2747         case 'X':
2748             im->unitsexponent = atoi(optarg);
2749             break;
2750         case 'S':
2751             im->step =  atoi(optarg);
2752             break;
2753         case 262:
2754             im->gridfit = 0;
2755             break;
2756         case 's':
2757             if ((parsetime_error = parsetime(optarg, &start_tv))) {
2758                 rrd_set_error( "start time: %s", parsetime_error );
2759                 return;
2760             }
2761             break;
2762         case 'e':
2763             if ((parsetime_error = parsetime(optarg, &end_tv))) {
2764                 rrd_set_error( "end time: %s", parsetime_error );
2765                 return;
2766             }
2767             break;
2768         case 'x':
2769             if(strcmp(optarg,"none") == 0){
2770               im->draw_x_grid=0;
2771               break;
2772             };
2773                 
2774             if(sscanf(optarg,
2775                       "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2776                       scan_gtm,
2777                       &im->xlab_user.gridst,
2778                       scan_mtm,
2779                       &im->xlab_user.mgridst,
2780                       scan_ltm,
2781                       &im->xlab_user.labst,
2782                       &im->xlab_user.precis,
2783                       &stroff) == 7 && stroff != 0){
2784                 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2785                 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2786                     rrd_set_error("unknown keyword %s",scan_gtm);
2787                     return;
2788                 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2789                     rrd_set_error("unknown keyword %s",scan_mtm);
2790                     return;
2791                 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2792                     rrd_set_error("unknown keyword %s",scan_ltm);
2793                     return;
2794                 } 
2795                 im->xlab_user.minsec = 1;
2796                 im->xlab_user.stst = im->xlab_form;
2797             } else {
2798                 rrd_set_error("invalid x-grid format");
2799                 return;
2800             }
2801             break;
2802         case 'y':
2803
2804             if(strcmp(optarg,"none") == 0){
2805               im->draw_y_grid=0;
2806               break;
2807             };
2808
2809             if(sscanf(optarg,
2810                       "%lf:%d",
2811                       &im->ygridstep,
2812                       &im->ylabfact) == 2) {
2813                 if(im->ygridstep<=0){
2814                     rrd_set_error("grid step must be > 0");
2815                     return;
2816                 } else if (im->ylabfact < 1){
2817                     rrd_set_error("label factor must be > 0");
2818                     return;
2819                 } 
2820             } else {
2821                 rrd_set_error("invalid y-grid format");
2822                 return;
2823             }
2824             break;
2825         case 'v':
2826             strncpy(im->ylegend,optarg,150);
2827             im->ylegend[150]='\0';
2828             break;
2829         case 'u':
2830             im->maxval = atof(optarg);
2831             break;
2832         case 'l':
2833             im->minval = atof(optarg);
2834             break;
2835         case 'b':
2836             im->base = atol(optarg);
2837             if(im->base != 1024 && im->base != 1000 ){
2838                 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2839                 return;
2840             }
2841             break;
2842         case 'w':
2843             long_tmp = atol(optarg);
2844             if (long_tmp < 10) {
2845                 rrd_set_error("width below 10 pixels");
2846                 return;
2847             }
2848             im->xsize = long_tmp;
2849             break;
2850         case 'h':
2851             long_tmp = atol(optarg);
2852             if (long_tmp < 10) {
2853                 rrd_set_error("height below 10 pixels");
2854                 return;
2855             }
2856             im->ysize = long_tmp;
2857             break;
2858         case 'i':
2859             im->canvas->interlaced = 1;
2860             break;
2861         case 'r':
2862             im->rigid = 1;
2863             break;
2864         case 'f':
2865             im->imginfo = optarg;
2866             break;
2867         case 'a':
2868             if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2869                 rrd_set_error("unsupported graphics format '%s'",optarg);
2870                 return;
2871             }
2872             break;
2873         case 'z':
2874             im->lazy = 1;
2875             break;
2876         case 'o':
2877             im->logarithmic = 1;
2878             if (isnan(im->minval))
2879                 im->minval=1;
2880             break;
2881         case 'c':
2882             if(sscanf(optarg,
2883                       "%10[A-Z]#%8lx",
2884                       col_nam,&color) == 2){
2885                 int ci;
2886                 if((ci=grc_conv(col_nam)) != -1){
2887                     im->graph_col[ci]=color;
2888                 }  else {
2889                   rrd_set_error("invalid color name '%s'",col_nam);
2890                 }
2891             } else {
2892                 rrd_set_error("invalid color def format");
2893                 return;
2894             }
2895             break;        
2896         case 'n':{
2897                         /* originally this used char *prop = "" and
2898                         ** char *font = "dummy" however this results
2899                         ** in a SEG fault, at least on RH7.1
2900                         **
2901                         ** The current implementation isn't proper
2902                         ** either, font is never freed and prop uses
2903                         ** a fixed width string
2904                         */
2905             char prop[100];
2906             double size = 1;
2907             char *font;
2908
2909             font=malloc(255);
2910             if(sscanf(optarg,
2911                                 "%10[A-Z]:%lf:%s",
2912                                 prop,&size,font) == 3){
2913                 int sindex;
2914                 if((sindex=text_prop_conv(prop)) != -1){
2915                     im->text_prop[sindex].size=size;              
2916                     im->text_prop[sindex].font=font;
2917                     if (sindex==0) { /* the default */
2918                         im->text_prop[TEXT_PROP_TITLE].size=size;
2919                         im->text_prop[TEXT_PROP_TITLE].font=font;
2920                         im->text_prop[TEXT_PROP_AXIS].size=size;
2921                         im->text_prop[TEXT_PROP_AXIS].font=font;
2922                         im->text_prop[TEXT_PROP_UNIT].size=size;
2923                         im->text_prop[TEXT_PROP_UNIT].font=font;
2924                         im->text_prop[TEXT_PROP_LEGEND].size=size;
2925                         im->text_prop[TEXT_PROP_LEGEND].font=font;
2926                     }
2927                 } else {
2928                     rrd_set_error("invalid fonttag '%s'",prop);
2929                     return;
2930                 }
2931             } else {
2932                 rrd_set_error("invalid text property format");
2933                 return;
2934             }
2935             break;          
2936         }
2937         case 'm':
2938             im->canvas->zoom = atof(optarg);
2939             if (im->canvas->zoom <= 0.0) {
2940                 rrd_set_error("zoom factor must be > 0");
2941                 return;
2942             }
2943           break;
2944         case 't':
2945             strncpy(im->title,optarg,150);
2946             im->title[150]='\0';
2947             break;
2948
2949         case '?':
2950             if (optopt != 0)
2951                 rrd_set_error("unknown option '%c'", optopt);
2952             else
2953                 rrd_set_error("unknown option '%s'",argv[optind-1]);
2954             return;
2955         }
2956     }
2957     
2958     if (optind >= argc) {
2959        rrd_set_error("missing filename");
2960        return;
2961     }
2962
2963     if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2964         rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");    
2965         return;
2966     }
2967
2968     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2969         /* error string is set in parsetime.c */
2970         return;
2971     }  
2972     
2973     if (start_tmp < 3600*24*365*10){
2974         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2975         return;
2976     }
2977     
2978     if (end_tmp < start_tmp) {
2979         rrd_set_error("start (%ld) should be less than end (%ld)", 
2980                start_tmp, end_tmp);
2981         return;
2982     }
2983     
2984     im->start = start_tmp;
2985     im->end = end_tmp;
2986 }
2987
2988 int
2989 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
2990 {
2991     if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
2992         rrd_set_error("Unknown variable '%s' in %s",varname,err);
2993         return -1;
2994     }
2995     return 0;
2996 }
2997 int
2998 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
2999 {
3000     char *color;
3001     graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3002
3003     color=strstr(var,"#");
3004     if (color==NULL) {
3005         if (optional==0) {
3006             rrd_set_error("Found no color in %s",err);
3007             return 0;
3008         }
3009         return 0;
3010     } else {
3011         int n=0;
3012         char *rest;
3013         gfx_color_t    col;
3014
3015         rest=strstr(color,":");
3016         if (rest!=NULL)
3017             n=rest-color;
3018         else
3019             n=strlen(color);
3020
3021         switch (n) {
3022             case 7:
3023                 sscanf(color,"#%6lx%n",&col,&n);
3024                 col = (col << 8) + 0xff /* shift left by 8 */;
3025                 if (n!=7) rrd_set_error("Color problem in %s",err);
3026                 break;
3027             case 9:
3028                 sscanf(color,"#%8lx%n",&col,&n);
3029                 if (n==9) break;
3030             default:
3031                 rrd_set_error("Color problem in %s",err);
3032         }
3033         if (rrd_test_error()) return 0;
3034         gdp->col = col;
3035         return n;
3036     }
3037 }
3038 int
3039 rrd_graph_legend(graph_desc_t *gdp, char *line)
3040 {
3041     int i;
3042
3043     i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3044
3045     return (strlen(&line[i])==0);
3046 }
3047
3048
3049 int bad_format(char *fmt) {
3050     char *ptr;
3051     int n=0;
3052     ptr = fmt;
3053     while (*ptr != '\0')
3054         if (*ptr++ == '%') {
3055  
3056              /* line cannot end with percent char */
3057              if (*ptr == '\0') return 1;
3058  
3059              /* '%s', '%S' and '%%' are allowed */
3060              if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3061
3062              /* or else '% 6.2lf' and such are allowed */
3063              else {
3064    
3065                  /* optional padding character */
3066                  if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3067   
3068                  /* This should take care of 'm.n' with all three optional */
3069                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3070                  if (*ptr == '.') ptr++;
3071                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3072   
3073                  /* Either 'le', 'lf' or 'lg' must follow here */
3074                  if (*ptr++ != 'l') return 1;
3075                  if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3076                  else return 1;
3077                  n++;
3078             }
3079          }
3080       
3081       return (n!=1); 
3082 }
3083
3084
3085 int
3086 vdef_parse(gdes,str)
3087 struct graph_desc_t *gdes;
3088 char *str;
3089 {
3090     /* A VDEF currently is either "func" or "param,func"
3091      * so the parsing is rather simple.  Change if needed.
3092      */
3093     double      param;
3094     char        func[30];
3095     int         n;
3096     
3097     n=0;
3098     sscanf(str,"%le,%29[A-Z]%n",&param,func,&n);
3099     if (n==strlen(str)) { /* matched */
3100         ;
3101     } else {
3102         n=0;
3103         sscanf(str,"%29[A-Z]%n",func,&n);
3104         if (n==strlen(str)) { /* matched */
3105             param=DNAN;
3106         } else {
3107             rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3108                 ,str
3109                 ,gdes->vname
3110                 );
3111             return -1;
3112         }
3113     }
3114     if          (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3115     else if     (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3116     else if     (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3117     else if     (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3118     else if     (!strcmp("TOTAL",  func)) gdes->vf.op = VDEF_TOTAL;
3119     else if     (!strcmp("FIRST",  func)) gdes->vf.op = VDEF_FIRST;
3120     else if     (!strcmp("LAST",   func)) gdes->vf.op = VDEF_LAST;
3121     else {
3122         rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3123             ,func
3124             ,gdes->vname
3125             );
3126         return -1;
3127     };
3128
3129     switch (gdes->vf.op) {
3130         case VDEF_PERCENT:
3131             if (isnan(param)) { /* no parameter given */
3132                 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3133                     ,func
3134                     ,gdes->vname
3135                     );
3136                 return -1;
3137             };
3138             if (param>=0.0 && param<=100.0) {
3139                 gdes->vf.param = param;
3140                 gdes->vf.val   = DNAN;  /* undefined */
3141                 gdes->vf.when  = 0;     /* undefined */
3142             } else {
3143                 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3144                     ,param
3145                     ,gdes->vname
3146                     );
3147                 return -1;
3148             };
3149             break;
3150         case VDEF_MAXIMUM:
3151         case VDEF_AVERAGE:
3152         case VDEF_MINIMUM:
3153         case VDEF_TOTAL:
3154         case VDEF_FIRST:
3155         case VDEF_LAST:
3156             if (isnan(param)) {
3157                 gdes->vf.param = DNAN;
3158                 gdes->vf.val   = DNAN;
3159                 gdes->vf.when  = 0;
3160             } else {
3161                 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3162                     ,func
3163                     ,gdes->vname
3164                     );
3165                 return -1;
3166             };
3167             break;
3168     };
3169     return 0;
3170 }
3171
3172
3173 int
3174 vdef_calc(im,gdi)
3175 image_desc_t *im;
3176 int gdi;
3177 {
3178     graph_desc_t        *src,*dst;
3179     rrd_value_t         *data;
3180     long                step,steps;
3181
3182     dst = &im->gdes[gdi];
3183     src = &im->gdes[dst->vidx];
3184     data = src->data + src->ds;
3185     steps = (src->end - src->start) / src->step;
3186
3187 #if 0
3188 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3189     ,src->start
3190     ,src->end
3191     ,steps
3192     );
3193 #endif
3194
3195     switch (dst->vf.op) {
3196         case VDEF_PERCENT: {
3197                 rrd_value_t *   array;
3198                 int             field;
3199
3200
3201                 if ((array = malloc(steps*sizeof(double)))==NULL) {
3202                     rrd_set_error("malloc VDEV_PERCENT");
3203                     return -1;
3204                 }
3205                 for (step=0;step < steps; step++) {
3206                     array[step]=data[step*src->ds_cnt];
3207                 }
3208                 qsort(array,step,sizeof(double),vdef_percent_compar);
3209
3210                 field = (steps-1)*dst->vf.param/100;
3211                 dst->vf.val  = array[field];
3212                 dst->vf.when = 0;       /* no time component */
3213                 free(array);
3214 #if 0
3215 for(step=0;step<steps;step++)
3216 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3217 #endif
3218             }
3219             break;
3220         case VDEF_MAXIMUM:
3221             step=0;
3222             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3223             if (step == steps) {
3224                 dst->vf.val  = DNAN;
3225                 dst->vf.when = 0;
3226             } else {
3227                 dst->vf.val  = data[step*src->ds_cnt];
3228                 dst->vf.when = src->start + (step+1)*src->step;
3229             }
3230             while (step != steps) {
3231                 if (finite(data[step*src->ds_cnt])) {
3232                     if (data[step*src->ds_cnt] > dst->vf.val) {
3233                         dst->vf.val  = data[step*src->ds_cnt];
3234                         dst->vf.when = src->start + (step+1)*src->step;
3235                     }
3236                 }
3237                 step++;
3238             }
3239             break;
3240         case VDEF_TOTAL:
3241         case VDEF_AVERAGE: {
3242             int cnt=0;
3243             double sum=0.0;
3244             for (step=0;step<steps;step++) {
3245                 if (finite(data[step*src->ds_cnt])) {
3246                     sum += data[step*src->ds_cnt];
3247                     cnt ++;
3248                 };
3249             }
3250             if (cnt) {
3251                 if (dst->vf.op == VDEF_TOTAL) {
3252                     dst->vf.val  = sum*src->step;
3253                     dst->vf.when = cnt*src->step;       /* not really "when" */
3254                 } else {
3255                     dst->vf.val = sum/cnt;
3256                     dst->vf.when = 0;   /* no time component */
3257                 };
3258             } else {
3259                 dst->vf.val  = DNAN;
3260                 dst->vf.when = 0;
3261             }
3262             }
3263             break;
3264         case VDEF_MINIMUM:
3265             step=0;
3266             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3267             if (step == steps) {
3268                 dst->vf.val  = DNAN;
3269                 dst->vf.when = 0;
3270             } else {
3271                 dst->vf.val  = data[step*src->ds_cnt];
3272                 dst->vf.when = src->start + (step+1)*src->step;
3273             }
3274             while (step != steps) {
3275                 if (finite(data[step*src->ds_cnt])) {
3276                     if (data[step*src->ds_cnt] < dst->vf.val) {
3277                         dst->vf.val  = data[step*src->ds_cnt];
3278                         dst->vf.when = src->start + (step+1)*src->step;
3279                     }
3280                 }
3281                 step++;
3282             }
3283             break;
3284         case VDEF_FIRST:
3285             /* The time value returned here is one step before the
3286              * actual time value.  This is the start of the first
3287              * non-NaN interval.
3288              */
3289             step=0;
3290             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3291             if (step == steps) { /* all entries were NaN */
3292                 dst->vf.val  = DNAN;
3293                 dst->vf.when = 0;
3294             } else {
3295                 dst->vf.val  = data[step*src->ds_cnt];
3296                 dst->vf.when = src->start + step*src->step;
3297             }
3298             break;
3299         case VDEF_LAST:
3300             /* The time value returned here is the
3301              * actual time value.  This is the end of the last
3302              * non-NaN interval.
3303              */
3304             step=steps-1;
3305             while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3306             if (step < 0) { /* all entries were NaN */
3307                 dst->vf.val  = DNAN;
3308                 dst->vf.when = 0;
3309             } else {
3310                 dst->vf.val  = data[step*src->ds_cnt];
3311                 dst->vf.when = src->start + (step+1)*src->step;
3312             }
3313             break;
3314     }
3315     return 0;
3316 }
3317
3318 /* NaN < -INF < finite_values < INF */
3319 int
3320 vdef_percent_compar(a,b)
3321 const void *a,*b;
3322 {
3323     /* Equality is not returned; this doesn't hurt except
3324      * (maybe) for a little performance.
3325      */
3326
3327     /* First catch NaN values. They are smallest */
3328     if (isnan( *(double *)a )) return -1;
3329     if (isnan( *(double *)b )) return  1;
3330
3331     /* NaN doesn't reach this part so INF and -INF are extremes.
3332      * The sign from isinf() is compatible with the sign we return
3333      */
3334     if (isinf( *(double *)a )) return isinf( *(double *)a );
3335     if (isinf( *(double *)b )) return isinf( *(double *)b );
3336
3337     /* If we reach this, both values must be finite */
3338     if ( *(double *)a < *(double *)b ) return -1; else return 1;
3339 }