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