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