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