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