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