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