make sure the font path is shorter than the buffer
[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      { 9.0, RRD_DEFAULT_FONT }, /* default */
41      { 11.0, RRD_DEFAULT_FONT }, /* title */
42      { 8.0,  RRD_DEFAULT_FONT }, /* axis */
43      { 9.0, RRD_DEFAULT_FONT }, /* unit */
44      { 9.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 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 GPRINT 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; /* skip empty legends */
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 unit 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_UNIT].font,
1919                   im->text_prop[TEXT_PROP_UNIT].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*1.2,
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_PRINT &&
1951                                    im->gdes[i].gf != GF_GPRINT &&
1952                                    im->gdes[i].gf != GF_COMMENT) {
1953                             int boxH, boxV;
1954                             
1955                             boxH = gfx_get_text_width(im->canvas, 0,
1956                                                       im->text_prop[TEXT_PROP_LEGEND].font,
1957                                                       im->text_prop[TEXT_PROP_LEGEND].size,
1958                                                       im->tabwidth,"M", 0)*1.2;
1959                             boxV = boxH;
1960                             
1961                             node = gfx_new_area(im->canvas,
1962                                                 X0,Y0-boxV,
1963                                                 X0,Y0,
1964                                                 X0+boxH,Y0,
1965                                                 im->gdes[i].col);
1966                             gfx_add_point ( node, X0+boxH, Y0-boxV );
1967                             node = gfx_new_line(im->canvas,
1968                                                 X0,Y0-boxV, X0,Y0,
1969                                                 1,0x000000FF);
1970                             gfx_add_point(node,X0+boxH,Y0);
1971                             gfx_add_point(node,X0+boxH,Y0-boxV);
1972                             gfx_close_path(node);
1973                     }
1974             }
1975     }
1976 }
1977
1978
1979 /*****************************************************
1980  * lazy check make sure we rely need to create this graph
1981  *****************************************************/
1982
1983 int lazy_check(image_desc_t *im){
1984     FILE *fd = NULL;
1985         int size = 1;
1986     struct stat  imgstat;
1987     
1988     if (im->lazy == 0) return 0; /* no lazy option */
1989     if (stat(im->graphfile,&imgstat) != 0) 
1990       return 0; /* can't stat */
1991     /* one pixel in the existing graph is more then what we would
1992        change here ... */
1993     if (time(NULL) - imgstat.st_mtime > 
1994         (im->end - im->start) / im->xsize) 
1995       return 0;
1996     if ((fd = fopen(im->graphfile,"rb")) == NULL) 
1997       return 0; /* the file does not exist */
1998     switch (im->canvas->imgformat) {
1999     case IF_PNG:
2000            size = PngSize(fd,&(im->ximg),&(im->yimg));
2001            break;
2002     default:
2003            size = 1;
2004     }
2005     fclose(fd);
2006     return size;
2007 }
2008
2009 void
2010 pie_part(image_desc_t *im, gfx_color_t color,
2011             double PieCenterX, double PieCenterY, double Radius,
2012             double startangle, double endangle)
2013 {
2014     gfx_node_t *node;
2015     double angle;
2016     double step=M_PI/50; /* Number of iterations for the circle;
2017                          ** 10 is definitely too low, more than
2018                          ** 50 seems to be overkill
2019                          */
2020
2021     /* Strange but true: we have to work clockwise or else
2022     ** anti aliasing nor transparency don't work.
2023     **
2024     ** This test is here to make sure we do it right, also
2025     ** this makes the for...next loop more easy to implement.
2026     ** The return will occur if the user enters a negative number
2027     ** (which shouldn't be done according to the specs) or if the
2028     ** programmers do something wrong (which, as we all know, never
2029     ** happens anyway :)
2030     */
2031     if (endangle<startangle) return;
2032
2033     /* Hidden feature: Radius decreases each full circle */
2034     angle=startangle;
2035     while (angle>=2*M_PI) {
2036         angle  -= 2*M_PI;
2037         Radius *= 0.8;
2038     }
2039
2040     node=gfx_new_area(im->canvas,
2041                 PieCenterX+sin(startangle)*Radius,
2042                 PieCenterY-cos(startangle)*Radius,
2043                 PieCenterX,
2044                 PieCenterY,
2045                 PieCenterX+sin(endangle)*Radius,
2046                 PieCenterY-cos(endangle)*Radius,
2047                 color);
2048     for (angle=endangle;angle-startangle>=step;angle-=step) {
2049         gfx_add_point(node,
2050                 PieCenterX+sin(angle)*Radius,
2051                 PieCenterY-cos(angle)*Radius );
2052     }
2053 }
2054
2055 int
2056 graph_size_location(image_desc_t *im, int elements, int piechart )
2057 {
2058     /* The actual size of the image to draw is determined from
2059     ** several sources.  The size given on the command line is
2060     ** the graph area but we need more as we have to draw labels
2061     ** and other things outside the graph area
2062     */
2063
2064     /* +-+-------------------------------------------+
2065     ** |l|.................title.....................|
2066     ** |e+--+-------------------------------+--------+
2067     ** |b| b|                               |        |
2068     ** |a| a|                               |  pie   |
2069     ** |l| l|          main graph area      | chart  |
2070     ** |.| .|                               |  area  |
2071     ** |t| y|                               |        |
2072     ** |r+--+-------------------------------+--------+
2073     ** |e|  | x-axis labels                 |        |
2074     ** |v+--+-------------------------------+--------+
2075     ** | |..............legends......................|
2076     ** +-+-------------------------------------------+
2077     */
2078     int Xvertical=0,    Yvertical=0,
2079         Xtitle   =0,    Ytitle   =0,
2080         Xylabel  =0,    Yylabel  =0,
2081         Xmain    =0,    Ymain    =0,
2082         Xpie     =0,    Ypie     =0,
2083         Xxlabel  =0,    Yxlabel  =0,
2084 #if 0
2085         Xlegend  =0,    Ylegend  =0,
2086 #endif
2087         Xspacing =10,  Yspacing =10;
2088
2089     if (im->extra_flags & ONLY_GRAPH) {
2090        Xspacing =0;
2091        Yspacing =0;
2092     } else {
2093         if (im->ylegend[0] != '\0') {
2094            Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2095            Yvertical = gfx_get_text_width(im->canvas, 0,
2096                                           im->text_prop[TEXT_PROP_UNIT].font,
2097                                           im->text_prop[TEXT_PROP_UNIT].size,
2098                                           im->tabwidth,im->ylegend, 0);
2099         }
2100     }
2101
2102     if (im->title[0] != '\0') {
2103         /* The title is placed "inbetween" two text lines so it
2104         ** automatically has some vertical spacing.  The horizontal
2105         ** spacing is added here, on each side.
2106         */
2107         Xtitle = gfx_get_text_width(im->canvas, 0,
2108                 im->text_prop[TEXT_PROP_TITLE].font,
2109                 im->text_prop[TEXT_PROP_TITLE].size,
2110                 im->tabwidth,
2111                 im->title, 0) + 2*Xspacing;
2112         Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.5;
2113     }
2114
2115     if (elements) {
2116         Xmain=im->xsize;
2117         Ymain=im->ysize;
2118         if (im->draw_x_grid) {
2119             Xxlabel=Xmain;
2120             Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2121         }
2122         if (im->draw_y_grid) {
2123             Xylabel=im->text_prop[TEXT_PROP_AXIS].size *6;
2124             Yylabel=Ymain;
2125         }
2126     }
2127
2128     if (piechart) {
2129         im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2130         Xpie=im->piesize;
2131         Ypie=im->piesize;
2132     }
2133
2134     /* Now calculate the total size.  Insert some spacing where
2135        desired.  im->xorigin and im->yorigin need to correspond
2136        with the lower left corner of the main graph area or, if
2137        this one is not set, the imaginary box surrounding the
2138        pie chart area. */
2139
2140     /* The legend width cannot yet be determined, as a result we
2141     ** have problems adjusting the image to it.  For now, we just
2142     ** forget about it at all; the legend will have to fit in the
2143     ** size already allocated.
2144     */
2145     im->ximg = Xmain;
2146
2147     if ( !(im->extra_flags & ONLY_GRAPH) ) {
2148         im->ximg = Xylabel + Xmain + Xpie + 2 * Xspacing;
2149     }
2150
2151     if (Xmain) im->ximg += Xspacing;
2152     if (Xpie) im->ximg += Xspacing;
2153
2154     if (im->extra_flags & ONLY_GRAPH) {
2155        im->xorigin = 0;
2156     } else {
2157        im->xorigin = Xspacing + Xylabel;
2158     }
2159
2160     if (Xtitle > im->ximg) im->ximg = Xtitle;
2161     if (Xvertical) {
2162         im->ximg += Xvertical;
2163         im->xorigin += Xvertical;
2164     }
2165     xtr(im,0);
2166
2167     /* The vertical size is interesting... we need to compare
2168     ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2169     ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2170     ** start even thinking about Ylegend.
2171     **
2172     ** Do it in three portions: First calculate the inner part,
2173     ** then do the legend, then adjust the total height of the img.
2174     */
2175
2176     /* reserve space for main and/or pie */
2177
2178     if (im->extra_flags & ONLY_GRAPH) {
2179         im->yimg = Ymain;
2180     } else {
2181         im->yimg = Ymain + Yxlabel;
2182     }
2183
2184     if (im->yimg < Ypie) im->yimg = Ypie;
2185
2186     if (im->extra_flags & ONLY_GRAPH) {
2187         im->yorigin = im->yimg;
2188     } else {
2189         im->yorigin = im->yimg - Yxlabel;
2190     }
2191
2192     /* reserve space for the title *or* some padding above the graph */
2193     if (Ytitle) {
2194         im->yimg += Ytitle;
2195         im->yorigin += Ytitle;
2196     } else {
2197         im->yimg += Yspacing;
2198         im->yorigin += Yspacing;
2199     }
2200     /* reserve space for padding below the graph */
2201     im->yimg += Yspacing;
2202     ytr(im,DNAN);
2203
2204     /* Determine where to place the legends onto the image.
2205     ** Adjust im->yimg to match the space requirements.
2206     */
2207     if(leg_place(im)==-1)
2208         return -1;
2209
2210     /* last of three steps: check total height of image */
2211     if (im->yimg < Yvertical) im->yimg = Yvertical;
2212
2213 #if 0
2214     if (Xlegend > im->ximg) {
2215         im->ximg = Xlegend;
2216         /* reposition Pie */
2217     }
2218 #endif
2219
2220     /* The pie is placed in the upper right hand corner,
2221     ** just below the title (if any) and with sufficient
2222     ** padding.
2223     */
2224     if (elements) {
2225         im->pie_x = im->ximg - Xspacing - Xpie/2;
2226         im->pie_y = im->yorigin-Ymain+Ypie/2;
2227     } else {
2228         im->pie_x = im->ximg/2;
2229         im->pie_y = im->yorigin-Ypie/2;
2230     }
2231
2232     return 0;
2233 }
2234
2235 /* draw that picture thing ... */
2236 int
2237 graph_paint(image_desc_t *im, char ***calcpr)
2238 {
2239   int i,ii;
2240   int lazy =     lazy_check(im);
2241   int piechart = 0;
2242   double PieStart=0.0;
2243   FILE  *fo;
2244   gfx_node_t *node;
2245   
2246   double areazero = 0.0;
2247   enum gf_en stack_gf = GF_PRINT;
2248   graph_desc_t *lastgdes = NULL;    
2249
2250   /* if we are lazy and there is nothing to PRINT ... quit now */
2251   if (lazy && im->prt_c==0) return 0;
2252
2253   /* pull the data from the rrd files ... */
2254   
2255   if(data_fetch(im)==-1)
2256     return -1;
2257
2258   /* evaluate VDEF and CDEF operations ... */
2259   if(data_calc(im)==-1)
2260     return -1;
2261   
2262   /* check if we need to draw a piechart */
2263   for(i=0;i<im->gdes_c;i++){
2264     if (im->gdes[i].gf == GF_PART) {
2265       piechart=1;
2266       break;
2267     }
2268   }
2269
2270   /* calculate and PRINT and GPRINT definitions. We have to do it at
2271    * this point because it will affect the length of the legends
2272    * if there are no graph elements we stop here ... 
2273    * if we are lazy, try to quit ... 
2274    */
2275   i=print_calc(im,calcpr);
2276   if(i<0) return -1;
2277   if(((i==0)&&(piechart==0)) || lazy) return 0;
2278
2279   /* If there's only the pie chart to draw, signal this */
2280   if (i==0) piechart=2;
2281   
2282   /* get actual drawing data and find min and max values*/
2283   if(data_proc(im)==-1)
2284     return -1;
2285   
2286   if(!im->logarithmic){si_unit(im);}        /* identify si magnitude Kilo, Mega Giga ? */
2287   
2288   if(!im->rigid && ! im->logarithmic)
2289     expand_range(im);   /* make sure the upper and lower limit are
2290                            sensible values */
2291
2292   if (!calc_horizontal_grid(im))
2293     return -1;
2294
2295   if (im->gridfit)
2296     apply_gridfit(im);
2297
2298
2299 /**************************************************************
2300  *** Calculating sizes and locations became a bit confusing ***
2301  *** so I moved this into a separate function.              ***
2302  **************************************************************/
2303   if(graph_size_location(im,i,piechart)==-1)
2304     return -1;
2305
2306   /* the actual graph is created by going through the individual
2307      graph elements and then drawing them */
2308   
2309   node=gfx_new_area ( im->canvas,
2310                       0, 0,
2311                       im->ximg, 0,
2312                       im->ximg, im->yimg,
2313                       im->graph_col[GRC_BACK]);
2314
2315   gfx_add_point(node,0, im->yimg);
2316
2317   if (piechart != 2) {
2318     node=gfx_new_area ( im->canvas,
2319                       im->xorigin,             im->yorigin, 
2320                       im->xorigin + im->xsize, im->yorigin,
2321                       im->xorigin + im->xsize, im->yorigin-im->ysize,
2322                       im->graph_col[GRC_CANVAS]);
2323   
2324     gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2325
2326     if (im->minval > 0.0)
2327       areazero = im->minval;
2328     if (im->maxval < 0.0)
2329       areazero = im->maxval;
2330   }
2331
2332   if (piechart) {
2333     pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2334   }
2335
2336   for(i=0;i<im->gdes_c;i++){
2337     switch(im->gdes[i].gf){
2338     case GF_CDEF:
2339     case GF_VDEF:
2340     case GF_DEF:
2341     case GF_PRINT:
2342     case GF_GPRINT:
2343     case GF_COMMENT:
2344     case GF_HRULE:
2345     case GF_VRULE:
2346     case GF_XPORT:
2347     case GF_SHIFT:
2348       break;
2349     case GF_TICK:
2350       for (ii = 0; ii < im->xsize; ii++)
2351         {
2352           if (!isnan(im->gdes[i].p_data[ii]) && 
2353               im->gdes[i].p_data[ii] > 0.0)
2354             { 
2355               /* generate a tick */
2356               gfx_new_line(im->canvas, im -> xorigin + ii, 
2357                            im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2358                            im -> xorigin + ii, 
2359                            im -> yorigin,
2360                            1.0,
2361                            im -> gdes[i].col );
2362             }
2363         }
2364       break;
2365     case GF_LINE:
2366     case GF_AREA:
2367       stack_gf = im->gdes[i].gf;
2368     case GF_STACK:          
2369       /* fix data points at oo and -oo */
2370       for(ii=0;ii<im->xsize;ii++){
2371         if (isinf(im->gdes[i].p_data[ii])){
2372           if (im->gdes[i].p_data[ii] > 0) {
2373             im->gdes[i].p_data[ii] = im->maxval ;
2374           } else {
2375             im->gdes[i].p_data[ii] = im->minval ;
2376           }                 
2377           
2378         }
2379       } /* for */
2380       
2381       if (im->gdes[i].col != 0x0){               
2382         /* GF_LINE and friend */
2383         if(stack_gf == GF_LINE ){
2384           node = NULL;
2385           for(ii=1;ii<im->xsize;ii++){
2386             if ( ! isnan(im->gdes[i].p_data[ii-1])
2387                  && ! isnan(im->gdes[i].p_data[ii])){
2388               if (node == NULL){
2389                 node = gfx_new_line(im->canvas,
2390                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2391                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2392                                     im->gdes[i].linewidth,
2393                                     im->gdes[i].col);
2394               } else {
2395                 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2396               }
2397             } else {
2398               node = NULL;
2399             }
2400           }
2401         } else {
2402           int area_start=-1;
2403           node = NULL;
2404           for(ii=1;ii<im->xsize;ii++){
2405             /* open an area */
2406             if ( ! isnan(im->gdes[i].p_data[ii-1])
2407                  && ! isnan(im->gdes[i].p_data[ii])){
2408               if (node == NULL){
2409                 float ybase = 0.0;
2410 /*
2411                 if (im->gdes[i].gf == GF_STACK) {
2412 */
2413                 if ( (im->gdes[i].gf == GF_STACK)
2414                   || (im->gdes[i].stack) ) {
2415
2416                   ybase = ytr(im,lastgdes->p_data[ii-1]);
2417                 } else {
2418                   ybase =  ytr(im,areazero);
2419                 }
2420                 area_start = ii-1;
2421                 node = gfx_new_area(im->canvas,
2422                                     ii-1+im->xorigin,ybase,
2423                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2424                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2425                                     im->gdes[i].col
2426                                     );
2427               } else {
2428                 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2429               }
2430             }
2431
2432             if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2433               /* GF_AREA STACK type*/
2434 /*
2435               if (im->gdes[i].gf == GF_STACK ) {
2436 */
2437               if ( (im->gdes[i].gf == GF_STACK)
2438                 || (im->gdes[i].stack) ) {
2439                 int iii;
2440                 for (iii=ii-1;iii>area_start;iii--){
2441                   gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2442                 }
2443               } else {
2444                 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2445               };
2446               node=NULL;
2447             };
2448           }             
2449         } /* else GF_LINE */
2450       } /* if color != 0x0 */
2451       /* make sure we do not run into trouble when stacking on NaN */
2452       for(ii=0;ii<im->xsize;ii++){
2453         if (isnan(im->gdes[i].p_data[ii])) {
2454           if (lastgdes && (im->gdes[i].gf == GF_STACK)) {
2455             im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2456           } else {
2457             im->gdes[i].p_data[ii] =  ytr(im,areazero);
2458           }
2459         }
2460       } 
2461       lastgdes = &(im->gdes[i]);                         
2462       break;
2463     case GF_PART:
2464       if(isnan(im->gdes[i].yrule)) /* fetch variable */
2465         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2466      
2467       if (finite(im->gdes[i].yrule)) {  /* even the fetched var can be NaN */
2468         pie_part(im,im->gdes[i].col,
2469                 im->pie_x,im->pie_y,im->piesize*0.4,
2470                 M_PI*2.0*PieStart/100.0,
2471                 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2472         PieStart += im->gdes[i].yrule;
2473       }
2474       break;
2475     } /* switch */
2476   }
2477   if (piechart==2) {
2478     im->draw_x_grid=0;
2479     im->draw_y_grid=0;
2480   }
2481
2482   if( !(im->extra_flags & ONLY_GRAPH) )  
2483       axis_paint(im);
2484
2485   /* grid_paint also does the text */
2486   if( !(im->extra_flags & ONLY_GRAPH) )  
2487     grid_paint(im);
2488   
2489   /* the RULES are the last thing to paint ... */
2490   for(i=0;i<im->gdes_c;i++){    
2491     
2492     switch(im->gdes[i].gf){
2493     case GF_HRULE:
2494       if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2495         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2496       };
2497       if(im->gdes[i].yrule >= im->minval
2498          && im->gdes[i].yrule <= im->maxval)
2499         gfx_new_line(im->canvas,
2500                      im->xorigin,ytr(im,im->gdes[i].yrule),
2501                      im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2502                      1.0,im->gdes[i].col); 
2503       break;
2504     case GF_VRULE:
2505       if(im->gdes[i].xrule == 0) { /* fetch variable */
2506         im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2507       };
2508       if(im->gdes[i].xrule >= im->start
2509          && im->gdes[i].xrule <= im->end)
2510         gfx_new_line(im->canvas,
2511                      xtr(im,im->gdes[i].xrule),im->yorigin,
2512                      xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2513                      1.0,im->gdes[i].col); 
2514       break;
2515     default:
2516       break;
2517     }
2518   }
2519
2520   
2521   if (strcmp(im->graphfile,"-")==0) {
2522     fo = im->graphhandle ? im->graphhandle : stdout;
2523 #ifdef WIN32
2524     /* Change translation mode for stdout to BINARY */
2525     _setmode( _fileno( fo ), O_BINARY );
2526 #endif
2527   } else {
2528     if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2529       rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2530                     rrd_strerror(errno));
2531       return (-1);
2532     }
2533   }
2534   gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2535   if (strcmp(im->graphfile,"-") != 0)
2536     fclose(fo);
2537   return 0;
2538 }
2539
2540
2541 /*****************************************************
2542  * graph stuff 
2543  *****************************************************/
2544
2545 int
2546 gdes_alloc(image_desc_t *im){
2547
2548     im->gdes_c++;
2549     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2550                                            * sizeof(graph_desc_t)))==NULL){
2551         rrd_set_error("realloc graph_descs");
2552         return -1;
2553     }
2554
2555
2556     im->gdes[im->gdes_c-1].step=im->step;
2557     im->gdes[im->gdes_c-1].stack=0;
2558     im->gdes[im->gdes_c-1].debug=0;
2559     im->gdes[im->gdes_c-1].start=im->start; 
2560     im->gdes[im->gdes_c-1].end=im->end; 
2561     im->gdes[im->gdes_c-1].vname[0]='\0'; 
2562     im->gdes[im->gdes_c-1].data=NULL;
2563     im->gdes[im->gdes_c-1].ds_namv=NULL;
2564     im->gdes[im->gdes_c-1].data_first=0;
2565     im->gdes[im->gdes_c-1].p_data=NULL;
2566     im->gdes[im->gdes_c-1].rpnp=NULL;
2567     im->gdes[im->gdes_c-1].shift=0;
2568     im->gdes[im->gdes_c-1].col = 0x0;
2569     im->gdes[im->gdes_c-1].legend[0]='\0';
2570     im->gdes[im->gdes_c-1].format[0]='\0';
2571     im->gdes[im->gdes_c-1].rrd[0]='\0';
2572     im->gdes[im->gdes_c-1].ds=-1;    
2573     im->gdes[im->gdes_c-1].p_data=NULL;    
2574     im->gdes[im->gdes_c-1].yrule=DNAN;
2575     im->gdes[im->gdes_c-1].xrule=0;
2576     return 0;
2577 }
2578
2579 /* copies input untill the first unescaped colon is found
2580    or until input ends. backslashes have to be escaped as well */
2581 int
2582 scan_for_col(char *input, int len, char *output)
2583 {
2584     int inp,outp=0;
2585     for (inp=0; 
2586          inp < len &&
2587            input[inp] != ':' &&
2588            input[inp] != '\0';
2589          inp++){
2590       if (input[inp] == '\\' &&
2591           input[inp+1] != '\0' && 
2592           (input[inp+1] == '\\' ||
2593            input[inp+1] == ':')){
2594         output[outp++] = input[++inp];
2595       }
2596       else {
2597         output[outp++] = input[inp];
2598       }
2599     }
2600     output[outp] = '\0';
2601     return inp;
2602 }
2603 /* Some surgery done on this function, it became ridiculously big.
2604 ** Things moved:
2605 ** - initializing     now in rrd_graph_init()
2606 ** - options parsing  now in rrd_graph_options()
2607 ** - script parsing   now in rrd_graph_script()
2608 */
2609 int 
2610 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
2611 {
2612     image_desc_t   im;
2613             
2614     rrd_graph_init(&im);
2615     im.graphhandle = stream;
2616     
2617     rrd_graph_options(argc,argv,&im);
2618     if (rrd_test_error()) {
2619         im_free(&im);
2620         return -1;
2621     }
2622     
2623     if (strlen(argv[optind])>=MAXPATH) {
2624         rrd_set_error("filename (including path) too long");
2625         im_free(&im);
2626         return -1;
2627     }
2628     strncpy(im.graphfile,argv[optind],MAXPATH-1);
2629     im.graphfile[MAXPATH-1]='\0';
2630
2631     rrd_graph_script(argc,argv,&im,1);
2632     if (rrd_test_error()) {
2633         im_free(&im);
2634         return -1;
2635     }
2636
2637     /* Everything is now read and the actual work can start */
2638
2639     (*prdata)=NULL;
2640     if (graph_paint(&im,prdata)==-1){
2641         im_free(&im);
2642         return -1;
2643     }
2644
2645     /* The image is generated and needs to be output.
2646     ** Also, if needed, print a line with information about the image.
2647     */
2648
2649     *xsize=im.ximg;
2650     *ysize=im.yimg;
2651     *ymin=im.minval;
2652     *ymax=im.maxval;
2653     if (im.imginfo) {
2654         char *filename;
2655         if (!(*prdata)) {
2656             /* maybe prdata is not allocated yet ... lets do it now */
2657             if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2658                 rrd_set_error("malloc imginfo");
2659                 return -1; 
2660             };
2661         }
2662         if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2663          ==NULL){
2664             rrd_set_error("malloc imginfo");
2665             return -1;
2666         }
2667         filename=im.graphfile+strlen(im.graphfile);
2668         while(filename > im.graphfile) {
2669             if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2670             filename--;
2671         }
2672
2673         sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2674     }
2675     im_free(&im);
2676     return 0;
2677 }
2678
2679 void
2680 rrd_graph_init(image_desc_t *im)
2681 {
2682     unsigned int i;
2683
2684 #ifdef HAVE_TZSET
2685     tzset();
2686 #endif
2687 #ifdef HAVE_SETLOCALE
2688     setlocale(LC_TIME,"");
2689 #endif
2690
2691     im->xlab_user.minsec = -1;
2692     im->ximg=0;
2693     im->yimg=0;
2694     im->xsize = 400;
2695     im->ysize = 100;
2696     im->step = 0;
2697     im->ylegend[0] = '\0';
2698     im->title[0] = '\0';
2699     im->minval = DNAN;
2700     im->maxval = DNAN;    
2701     im->unitsexponent= 9999;
2702     im->extra_flags= 0;
2703     im->rigid = 0;
2704     im->gridfit = 1;
2705     im->imginfo = NULL;
2706     im->lazy = 0;
2707     im->logarithmic = 0;
2708     im->ygridstep = DNAN;
2709     im->draw_x_grid = 1;
2710     im->draw_y_grid = 1;
2711     im->base = 1000;
2712     im->prt_c = 0;
2713     im->gdes_c = 0;
2714     im->gdes = NULL;
2715     im->canvas = gfx_new_canvas();
2716     im->grid_dash_on = 1;
2717     im->grid_dash_off = 1;
2718     im->tabwidth = 40.0;
2719     
2720     for(i=0;i<DIM(graph_col);i++)
2721         im->graph_col[i]=graph_col[i];
2722 #ifdef WIN32
2723     {
2724             char *windir; 
2725             windir = getenv("windir");
2726             /* %windir% is something like D:\windows or C:\winnt */
2727             if (windir != NULL) {
2728                     strcpy(rrd_win_default_font,windir);
2729                     strcat(rrd_win_default_font,"\\fonts\\cour.ttf");
2730                     for(i=0;i<DIM(text_prop);i++)
2731                             strcpy(text_prop[i].font,rrd_win_default_font);
2732             }
2733     }
2734 #endif
2735     for(i=0;i<DIM(text_prop);i++){        
2736       im->text_prop[i].size = text_prop[i].size;
2737       strcpy(im->text_prop[i].font,text_prop[i].font);
2738     }
2739 }
2740
2741 void
2742 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2743 {
2744     int                 stroff;    
2745     char                *parsetime_error = NULL;
2746     char                scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2747     time_t              start_tmp=0,end_tmp=0;
2748     long                long_tmp;
2749     struct rrd_time_value       start_tv, end_tv;
2750     gfx_color_t         color;
2751
2752     parsetime("end-24h", &start_tv);
2753     parsetime("now", &end_tv);
2754
2755     while (1){
2756         static struct option long_options[] =
2757         {
2758             {"start",      required_argument, 0,  's'},
2759             {"end",        required_argument, 0,  'e'},
2760             {"x-grid",     required_argument, 0,  'x'},
2761             {"y-grid",     required_argument, 0,  'y'},
2762             {"vertical-label",required_argument,0,'v'},
2763             {"width",      required_argument, 0,  'w'},
2764             {"height",     required_argument, 0,  'h'},
2765             {"interlaced", no_argument,       0,  'i'},
2766             {"upper-limit",required_argument, 0,  'u'},
2767             {"lower-limit",required_argument, 0,  'l'},
2768             {"rigid",      no_argument,       0,  'r'},
2769             {"base",       required_argument, 0,  'b'},
2770             {"logarithmic",no_argument,       0,  'o'},
2771             {"color",      required_argument, 0,  'c'},
2772             {"font",       required_argument, 0,  'n'},
2773             {"title",      required_argument, 0,  't'},
2774             {"imginfo",    required_argument, 0,  'f'},
2775             {"imgformat",  required_argument, 0,  'a'},
2776             {"lazy",       no_argument,       0,  'z'},
2777             {"zoom",       required_argument, 0,  'm'},
2778             {"no-legend",  no_argument,       0,  'g'},
2779             {"force-rules-legend",no_argument,0,  'F'},
2780             {"only-graph", no_argument,       0,  'j'},
2781             {"alt-y-grid", no_argument,       0,  'Y'},
2782             {"no-minor",   no_argument,       0,  'I'},
2783             {"alt-autoscale", no_argument,    0,  'A'},
2784             {"alt-autoscale-max", no_argument, 0, 'M'},
2785             {"units-exponent",required_argument, 0, 'X'},
2786             {"step",       required_argument, 0,    'S'},
2787             {"tabwidth",   required_argument, 0,    'T'},            
2788             {"no-gridfit", no_argument,       0,   'N'},
2789             {0,0,0,0}};
2790         int option_index = 0;
2791         int opt;
2792
2793
2794         opt = getopt_long(argc, argv, 
2795                          "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMX:S:NT:",
2796                           long_options, &option_index);
2797
2798         if (opt == EOF)
2799             break;
2800         
2801         switch(opt) {
2802         case 'I':
2803             im->extra_flags |= NOMINOR;
2804             break;
2805         case 'Y':
2806             im->extra_flags |= ALTYGRID;
2807             break;
2808         case 'A':
2809             im->extra_flags |= ALTAUTOSCALE;
2810             break;
2811         case 'M':
2812             im->extra_flags |= ALTAUTOSCALE_MAX;
2813             break;
2814         case 'j':
2815            im->extra_flags |= ONLY_GRAPH;
2816            break;
2817         case 'g':
2818             im->extra_flags |= NOLEGEND;
2819             break;
2820         case 'F':
2821             im->extra_flags |= FORCE_RULES_LEGEND;
2822             break;
2823         case 'X':
2824             im->unitsexponent = atoi(optarg);
2825             break;
2826         case 'T':
2827             im->tabwidth = atof(optarg);
2828             break;
2829         case 'S':
2830             im->step =  atoi(optarg);
2831             break;
2832         case 262:
2833             im->gridfit = 0;
2834             break;
2835         case 's':
2836             if ((parsetime_error = parsetime(optarg, &start_tv))) {
2837                 rrd_set_error( "start time: %s", parsetime_error );
2838                 return;
2839             }
2840             break;
2841         case 'e':
2842             if ((parsetime_error = parsetime(optarg, &end_tv))) {
2843                 rrd_set_error( "end time: %s", parsetime_error );
2844                 return;
2845             }
2846             break;
2847         case 'x':
2848             if(strcmp(optarg,"none") == 0){
2849               im->draw_x_grid=0;
2850               break;
2851             };
2852                 
2853             if(sscanf(optarg,
2854                       "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2855                       scan_gtm,
2856                       &im->xlab_user.gridst,
2857                       scan_mtm,
2858                       &im->xlab_user.mgridst,
2859                       scan_ltm,
2860                       &im->xlab_user.labst,
2861                       &im->xlab_user.precis,
2862                       &stroff) == 7 && stroff != 0){
2863                 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2864                 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2865                     rrd_set_error("unknown keyword %s",scan_gtm);
2866                     return;
2867                 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2868                     rrd_set_error("unknown keyword %s",scan_mtm);
2869                     return;
2870                 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2871                     rrd_set_error("unknown keyword %s",scan_ltm);
2872                     return;
2873                 } 
2874                 im->xlab_user.minsec = 1;
2875                 im->xlab_user.stst = im->xlab_form;
2876             } else {
2877                 rrd_set_error("invalid x-grid format");
2878                 return;
2879             }
2880             break;
2881         case 'y':
2882
2883             if(strcmp(optarg,"none") == 0){
2884               im->draw_y_grid=0;
2885               break;
2886             };
2887
2888             if(sscanf(optarg,
2889                       "%lf:%d",
2890                       &im->ygridstep,
2891                       &im->ylabfact) == 2) {
2892                 if(im->ygridstep<=0){
2893                     rrd_set_error("grid step must be > 0");
2894                     return;
2895                 } else if (im->ylabfact < 1){
2896                     rrd_set_error("label factor must be > 0");
2897                     return;
2898                 } 
2899             } else {
2900                 rrd_set_error("invalid y-grid format");
2901                 return;
2902             }
2903             break;
2904         case 'v':
2905             strncpy(im->ylegend,optarg,150);
2906             im->ylegend[150]='\0';
2907             break;
2908         case 'u':
2909             im->maxval = atof(optarg);
2910             break;
2911         case 'l':
2912             im->minval = atof(optarg);
2913             break;
2914         case 'b':
2915             im->base = atol(optarg);
2916             if(im->base != 1024 && im->base != 1000 ){
2917                 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2918                 return;
2919             }
2920             break;
2921         case 'w':
2922             long_tmp = atol(optarg);
2923             if (long_tmp < 10) {
2924                 rrd_set_error("width below 10 pixels");
2925                 return;
2926             }
2927             im->xsize = long_tmp;
2928             break;
2929         case 'h':
2930             long_tmp = atol(optarg);
2931             if (long_tmp < 10) {
2932                 rrd_set_error("height below 10 pixels");
2933                 return;
2934             }
2935             im->ysize = long_tmp;
2936             break;
2937         case 'i':
2938             im->canvas->interlaced = 1;
2939             break;
2940         case 'r':
2941             im->rigid = 1;
2942             break;
2943         case 'f':
2944             im->imginfo = optarg;
2945             break;
2946         case 'a':
2947             if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
2948                 rrd_set_error("unsupported graphics format '%s'",optarg);
2949                 return;
2950             }
2951             break;
2952         case 'z':
2953             im->lazy = 1;
2954             break;
2955         case 'o':
2956             im->logarithmic = 1;
2957             if (isnan(im->minval))
2958                 im->minval=1;
2959             break;
2960         case 'c':
2961             if(sscanf(optarg,
2962                       "%10[A-Z]#%8lx",
2963                       col_nam,&color) == 2){
2964                 int ci;
2965                 if((ci=grc_conv(col_nam)) != -1){
2966                     im->graph_col[ci]=color;
2967                 }  else {
2968                   rrd_set_error("invalid color name '%s'",col_nam);
2969                 }
2970             } else {
2971                 rrd_set_error("invalid color def format");
2972                 return;
2973             }
2974             break;        
2975         case 'n':{
2976             char prop[15];
2977             double size = 1;
2978             char font[1024];
2979
2980             if(sscanf(optarg,
2981                                 "%10[A-Z]:%lf:%1000s",
2982                                 prop,&size,font) == 3){
2983                 int sindex;
2984                 if((sindex=text_prop_conv(prop)) != -1){
2985                     im->text_prop[sindex].size=size;              
2986                     strcpy(im->text_prop[sindex].font,font);
2987                     if (sindex==0) { /* the default */
2988                         im->text_prop[TEXT_PROP_TITLE].size=size;
2989                         strcpy(im->text_prop[TEXT_PROP_TITLE].font,font);
2990                         im->text_prop[TEXT_PROP_AXIS].size=size;
2991                         strcpy(im->text_prop[TEXT_PROP_AXIS].font,font);
2992                         im->text_prop[TEXT_PROP_UNIT].size=size;
2993                         strcpy(im->text_prop[TEXT_PROP_UNIT].font,font);
2994                         im->text_prop[TEXT_PROP_LEGEND].size=size;
2995                         strcpy(im->text_prop[TEXT_PROP_LEGEND].font,font);
2996                     }
2997                 } else {
2998                     rrd_set_error("invalid fonttag '%s'",prop);
2999                     return;
3000                 }
3001             } else {
3002                 rrd_set_error("invalid text property format");
3003                 return;
3004             }
3005             break;          
3006         }
3007         case 'm':
3008             im->canvas->zoom = atof(optarg);
3009             if (im->canvas->zoom <= 0.0) {
3010                 rrd_set_error("zoom factor must be > 0");
3011                 return;
3012             }
3013           break;
3014         case 't':
3015             strncpy(im->title,optarg,150);
3016             im->title[150]='\0';
3017             break;
3018
3019         case '?':
3020             if (optopt != 0)
3021                 rrd_set_error("unknown option '%c'", optopt);
3022             else
3023                 rrd_set_error("unknown option '%s'",argv[optind-1]);
3024             return;
3025         }
3026     }
3027     
3028     if (optind >= argc) {
3029        rrd_set_error("missing filename");
3030        return;
3031     }
3032
3033     if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
3034         rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");    
3035         return;
3036     }
3037
3038     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3039         /* error string is set in parsetime.c */
3040         return;
3041     }  
3042     
3043     if (start_tmp < 3600*24*365*10){
3044         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3045         return;
3046     }
3047     
3048     if (end_tmp < start_tmp) {
3049         rrd_set_error("start (%ld) should be less than end (%ld)", 
3050                start_tmp, end_tmp);
3051         return;
3052     }
3053     
3054     im->start = start_tmp;
3055     im->end = end_tmp;
3056     im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3057 }
3058
3059 int
3060 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3061 {
3062     if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3063         rrd_set_error("Unknown variable '%s' in %s",varname,err);
3064         return -1;
3065     }
3066     return 0;
3067 }
3068 int
3069 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3070 {
3071     char *color;
3072     graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3073
3074     color=strstr(var,"#");
3075     if (color==NULL) {
3076         if (optional==0) {
3077             rrd_set_error("Found no color in %s",err);
3078             return 0;
3079         }
3080         return 0;
3081     } else {
3082         int n=0;
3083         char *rest;
3084         gfx_color_t    col;
3085
3086         rest=strstr(color,":");
3087         if (rest!=NULL)
3088             n=rest-color;
3089         else
3090             n=strlen(color);
3091
3092         switch (n) {
3093             case 7:
3094                 sscanf(color,"#%6lx%n",&col,&n);
3095                 col = (col << 8) + 0xff /* shift left by 8 */;
3096                 if (n!=7) rrd_set_error("Color problem in %s",err);
3097                 break;
3098             case 9:
3099                 sscanf(color,"#%8lx%n",&col,&n);
3100                 if (n==9) break;
3101             default:
3102                 rrd_set_error("Color problem in %s",err);
3103         }
3104         if (rrd_test_error()) return 0;
3105         gdp->col = col;
3106         return n;
3107     }
3108 }
3109
3110
3111 int bad_format(char *fmt) {
3112     char *ptr;
3113     int n=0;
3114     ptr = fmt;
3115     while (*ptr != '\0')
3116         if (*ptr++ == '%') {
3117  
3118              /* line cannot end with percent char */
3119              if (*ptr == '\0') return 1;
3120  
3121              /* '%s', '%S' and '%%' are allowed */
3122              if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3123
3124              /* or else '% 6.2lf' and such are allowed */
3125              else {
3126    
3127                  /* optional padding character */
3128                  if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3129   
3130                  /* This should take care of 'm.n' with all three optional */
3131                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3132                  if (*ptr == '.') ptr++;
3133                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3134   
3135                  /* Either 'le', 'lf' or 'lg' must follow here */
3136                  if (*ptr++ != 'l') return 1;
3137                  if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3138                  else return 1;
3139                  n++;
3140             }
3141          }
3142       
3143       return (n!=1); 
3144 }
3145
3146
3147 int
3148 vdef_parse(gdes,str)
3149 struct graph_desc_t *gdes;
3150 char *str;
3151 {
3152     /* A VDEF currently is either "func" or "param,func"
3153      * so the parsing is rather simple.  Change if needed.
3154      */
3155     double      param;
3156     char        func[30];
3157     int         n;
3158     
3159     n=0;
3160     sscanf(str,"%le,%29[A-Z]%n",&param,func,&n);
3161     if (n== (int)strlen(str)) { /* matched */
3162         ;
3163     } else {
3164         n=0;
3165         sscanf(str,"%29[A-Z]%n",func,&n);
3166         if (n== (int)strlen(str)) { /* matched */
3167             param=DNAN;
3168         } else {
3169             rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3170                 ,str
3171                 ,gdes->vname
3172                 );
3173             return -1;
3174         }
3175     }
3176     if          (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3177     else if     (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3178     else if     (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3179     else if     (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3180     else if     (!strcmp("TOTAL",  func)) gdes->vf.op = VDEF_TOTAL;
3181     else if     (!strcmp("FIRST",  func)) gdes->vf.op = VDEF_FIRST;
3182     else if     (!strcmp("LAST",   func)) gdes->vf.op = VDEF_LAST;
3183     else {
3184         rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3185             ,func
3186             ,gdes->vname
3187             );
3188         return -1;
3189     };
3190
3191     switch (gdes->vf.op) {
3192         case VDEF_PERCENT:
3193             if (isnan(param)) { /* no parameter given */
3194                 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3195                     ,func
3196                     ,gdes->vname
3197                     );
3198                 return -1;
3199             };
3200             if (param>=0.0 && param<=100.0) {
3201                 gdes->vf.param = param;
3202                 gdes->vf.val   = DNAN;  /* undefined */
3203                 gdes->vf.when  = 0;     /* undefined */
3204             } else {
3205                 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3206                     ,param
3207                     ,gdes->vname
3208                     );
3209                 return -1;
3210             };
3211             break;
3212         case VDEF_MAXIMUM:
3213         case VDEF_AVERAGE:
3214         case VDEF_MINIMUM:
3215         case VDEF_TOTAL:
3216         case VDEF_FIRST:
3217         case VDEF_LAST:
3218             if (isnan(param)) {
3219                 gdes->vf.param = DNAN;
3220                 gdes->vf.val   = DNAN;
3221                 gdes->vf.when  = 0;
3222             } else {
3223                 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3224                     ,func
3225                     ,gdes->vname
3226                     );
3227                 return -1;
3228             };
3229             break;
3230     };
3231     return 0;
3232 }
3233
3234
3235 int
3236 vdef_calc(im,gdi)
3237 image_desc_t *im;
3238 int gdi;
3239 {
3240     graph_desc_t        *src,*dst;
3241     rrd_value_t         *data;
3242     long                step,steps;
3243
3244     dst = &im->gdes[gdi];
3245     src = &im->gdes[dst->vidx];
3246     data = src->data + src->ds;
3247     steps = (src->end - src->start) / src->step;
3248
3249 #if 0
3250 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3251     ,src->start
3252     ,src->end
3253     ,steps
3254     );
3255 #endif
3256
3257     switch (dst->vf.op) {
3258         case VDEF_PERCENT: {
3259                 rrd_value_t *   array;
3260                 int             field;
3261
3262
3263                 if ((array = malloc(steps*sizeof(double)))==NULL) {
3264                     rrd_set_error("malloc VDEV_PERCENT");
3265                     return -1;
3266                 }
3267                 for (step=0;step < steps; step++) {
3268                     array[step]=data[step*src->ds_cnt];
3269                 }
3270                 qsort(array,step,sizeof(double),vdef_percent_compar);
3271
3272                 field = (steps-1)*dst->vf.param/100;
3273                 dst->vf.val  = array[field];
3274                 dst->vf.when = 0;       /* no time component */
3275                 free(array);
3276 #if 0
3277 for(step=0;step<steps;step++)
3278 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3279 #endif
3280             }
3281             break;
3282         case VDEF_MAXIMUM:
3283             step=0;
3284             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3285             if (step == steps) {
3286                 dst->vf.val  = DNAN;
3287                 dst->vf.when = 0;
3288             } else {
3289                 dst->vf.val  = data[step*src->ds_cnt];
3290                 dst->vf.when = src->start + (step+1)*src->step;
3291             }
3292             while (step != steps) {
3293                 if (finite(data[step*src->ds_cnt])) {
3294                     if (data[step*src->ds_cnt] > dst->vf.val) {
3295                         dst->vf.val  = data[step*src->ds_cnt];
3296                         dst->vf.when = src->start + (step+1)*src->step;
3297                     }
3298                 }
3299                 step++;
3300             }
3301             break;
3302         case VDEF_TOTAL:
3303         case VDEF_AVERAGE: {
3304             int cnt=0;
3305             double sum=0.0;
3306             for (step=0;step<steps;step++) {
3307                 if (finite(data[step*src->ds_cnt])) {
3308                     sum += data[step*src->ds_cnt];
3309                     cnt ++;
3310                 };
3311             }
3312             if (cnt) {
3313                 if (dst->vf.op == VDEF_TOTAL) {
3314                     dst->vf.val  = sum*src->step;
3315                     dst->vf.when = cnt*src->step;       /* not really "when" */
3316                 } else {
3317                     dst->vf.val = sum/cnt;
3318                     dst->vf.when = 0;   /* no time component */
3319                 };
3320             } else {
3321                 dst->vf.val  = DNAN;
3322                 dst->vf.when = 0;
3323             }
3324             }
3325             break;
3326         case VDEF_MINIMUM:
3327             step=0;
3328             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3329             if (step == steps) {
3330                 dst->vf.val  = DNAN;
3331                 dst->vf.when = 0;
3332             } else {
3333                 dst->vf.val  = data[step*src->ds_cnt];
3334                 dst->vf.when = src->start + (step+1)*src->step;
3335             }
3336             while (step != steps) {
3337                 if (finite(data[step*src->ds_cnt])) {
3338                     if (data[step*src->ds_cnt] < dst->vf.val) {
3339                         dst->vf.val  = data[step*src->ds_cnt];
3340                         dst->vf.when = src->start + (step+1)*src->step;
3341                     }
3342                 }
3343                 step++;
3344             }
3345             break;
3346         case VDEF_FIRST:
3347             /* The time value returned here is one step before the
3348              * actual time value.  This is the start of the first
3349              * non-NaN interval.
3350              */
3351             step=0;
3352             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3353             if (step == steps) { /* all entries were NaN */
3354                 dst->vf.val  = DNAN;
3355                 dst->vf.when = 0;
3356             } else {
3357                 dst->vf.val  = data[step*src->ds_cnt];
3358                 dst->vf.when = src->start + step*src->step;
3359             }
3360             break;
3361         case VDEF_LAST:
3362             /* The time value returned here is the
3363              * actual time value.  This is the end of the last
3364              * non-NaN interval.
3365              */
3366             step=steps-1;
3367             while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3368             if (step < 0) { /* all entries were NaN */
3369                 dst->vf.val  = DNAN;
3370                 dst->vf.when = 0;
3371             } else {
3372                 dst->vf.val  = data[step*src->ds_cnt];
3373                 dst->vf.when = src->start + (step+1)*src->step;
3374             }
3375             break;
3376     }
3377     return 0;
3378 }
3379
3380 /* NaN < -INF < finite_values < INF */
3381 int
3382 vdef_percent_compar(a,b)
3383 const void *a,*b;
3384 {
3385     /* Equality is not returned; this doesn't hurt except
3386      * (maybe) for a little performance.
3387      */
3388
3389     /* First catch NaN values. They are smallest */
3390     if (isnan( *(double *)a )) return -1;
3391     if (isnan( *(double *)b )) return  1;
3392
3393     /* NaN doesn't reach this part so INF and -INF are extremes.
3394      * The sign from isinf() is compatible with the sign we return
3395      */
3396     if (isinf( *(double *)a )) return isinf( *(double *)a );
3397     if (isinf( *(double *)b )) return isinf( *(double *)b );
3398
3399     /* If we reach this, both values must be finite */
3400     if ( *(double *)a < *(double *)b ) return -1; else return 1;
3401 }