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