watermartk feature for rrdgraph by Ronan Mullally
[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     double X0=im->xorigin;
1616     double X1=im->xorigin+im->xsize;
1617    
1618     int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1619     int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1620     double MaxY;
1621     scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1622     MaxY = scaledstep*(double)egrid;
1623     for (i = sgrid; i <= egrid; i++){
1624        double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1625        if ( Y0 >= im->yorigin-im->ysize
1626                  && Y0 <= im->yorigin){       
1627             if(i % im->ygrid_scale.labfact == 0){               
1628                 if (im->symbol == ' ') {
1629                     if(im->extra_flags & ALTYGRID) {
1630                         sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1631                     } else {
1632                         if(MaxY < 10) {
1633                            sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1634                         } else {
1635                            sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1636                         }
1637                     }
1638                 }else {
1639                     char sisym = ( i == 0  ? ' ' : im->symbol);
1640                     if(im->extra_flags & ALTYGRID) {
1641                         sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1642                     } else {
1643                         if(MaxY < 10){
1644                           sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1645                         } else {
1646                           sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1647                         }
1648                     }
1649                 }
1650
1651                gfx_new_text ( im->canvas,
1652                               X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1653                               im->graph_col[GRC_FONT],
1654                               im->text_prop[TEXT_PROP_AXIS].font,
1655                               im->text_prop[TEXT_PROP_AXIS].size,
1656                               im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1657                               graph_label );
1658                gfx_new_dashed_line ( im->canvas,
1659                               X0-2,Y0,
1660                               X1+2,Y0,
1661                               MGRIDWIDTH, im->graph_col[GRC_MGRID],
1662                               im->grid_dash_on, im->grid_dash_off);            
1663                
1664             } else if (!(im->extra_flags & NOMINOR)) {          
1665                gfx_new_dashed_line ( im->canvas,
1666                               X0-1,Y0,
1667                               X1+1,Y0,
1668                               GRIDWIDTH, im->graph_col[GRC_GRID],
1669                               im->grid_dash_on, im->grid_dash_off);            
1670                
1671             }       
1672         }       
1673     } 
1674     return 1;
1675 }
1676
1677 /* logaritmic horizontal grid */
1678 int
1679 horizontal_log_grid(image_desc_t   *im)   
1680 {
1681     double   pixpex;
1682     int      ii,i;
1683     int      minoridx=0, majoridx=0;
1684     char     graph_label[100];
1685     double   X0,X1,Y0;   
1686     double   value, pixperstep, minstep;
1687
1688     /* find grid spaceing */
1689     pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1690
1691         if (isnan(pixpex)) {
1692                 return 0;
1693         }
1694
1695     for(i=0;yloglab[i][0] > 0;i++){
1696         minstep = log10(yloglab[i][0]);
1697         for(ii=1;yloglab[i][ii+1] > 0;ii++){
1698             if(yloglab[i][ii+2]==0){
1699                 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1700                 break;
1701             }
1702         }
1703         pixperstep = pixpex * minstep;
1704         if(pixperstep > 5){minoridx = i;}
1705        if(pixperstep > 2 *  im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1706     }
1707    
1708    X0=im->xorigin;
1709    X1=im->xorigin+im->xsize;
1710     /* paint minor grid */
1711     for (value = pow((double)10, log10(im->minval) 
1712                           - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1713          value  <= im->maxval;
1714          value *= yloglab[minoridx][0]){
1715         if (value < im->minval) continue;
1716         i=0;    
1717         while(yloglab[minoridx][++i] > 0){          
1718            Y0 = ytr(im,value * yloglab[minoridx][i]);
1719            if (Y0 <= im->yorigin - im->ysize) break;
1720            gfx_new_dashed_line ( im->canvas,
1721                           X0-1,Y0,
1722                           X1+1,Y0,
1723                           GRIDWIDTH, im->graph_col[GRC_GRID],
1724                           im->grid_dash_on, im->grid_dash_off);
1725         }
1726     }
1727
1728     /* paint major grid and labels*/
1729     for (value = pow((double)10, log10(im->minval) 
1730                           - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1731          value <= im->maxval;
1732          value *= yloglab[majoridx][0]){
1733         if (value < im->minval) continue;
1734         i=0;    
1735         while(yloglab[majoridx][++i] > 0){          
1736            Y0 = ytr(im,value * yloglab[majoridx][i]);    
1737            if (Y0 <= im->yorigin - im->ysize) break;
1738            gfx_new_dashed_line ( im->canvas,
1739                           X0-2,Y0,
1740                           X1+2,Y0,
1741                           MGRIDWIDTH, im->graph_col[GRC_MGRID],
1742                           im->grid_dash_on, im->grid_dash_off);
1743            
1744            sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1745            gfx_new_text ( im->canvas,
1746                           X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1747                           im->graph_col[GRC_FONT],
1748                           im->text_prop[TEXT_PROP_AXIS].font,
1749                           im->text_prop[TEXT_PROP_AXIS].size,
1750                           im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1751                           graph_label );
1752         } 
1753     }
1754         return 1;
1755 }
1756
1757
1758 void
1759 vertical_grid(
1760     image_desc_t   *im )
1761 {   
1762     int xlab_sel;               /* which sort of label and grid ? */
1763     time_t ti, tilab, timajor;
1764     long factor;
1765     char graph_label[100];
1766     double X0,Y0,Y1; /* points for filled graph and more*/
1767     struct tm tm;
1768
1769     /* the type of time grid is determined by finding
1770        the number of seconds per pixel in the graph */
1771     
1772     
1773     if(im->xlab_user.minsec == -1){
1774         factor=(im->end - im->start)/im->xsize;
1775         xlab_sel=0;
1776         while ( xlab[xlab_sel+1].minsec != -1 
1777                 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; }   /* pick the last one */
1778         while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
1779                 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; }       /* go back to the smallest size */
1780         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1781         im->xlab_user.gridst = xlab[xlab_sel].gridst;
1782         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1783         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1784         im->xlab_user.labtm = xlab[xlab_sel].labtm;
1785         im->xlab_user.labst = xlab[xlab_sel].labst;
1786         im->xlab_user.precis = xlab[xlab_sel].precis;
1787         im->xlab_user.stst = xlab[xlab_sel].stst;
1788     }
1789     
1790     /* y coords are the same for every line ... */
1791     Y0 = im->yorigin;
1792     Y1 = im->yorigin-im->ysize;
1793    
1794
1795     /* paint the minor grid */
1796     if (!(im->extra_flags & NOMINOR))
1797     {
1798         for(ti = find_first_time(im->start,
1799                                 im->xlab_user.gridtm,
1800                                 im->xlab_user.gridst),
1801             timajor = find_first_time(im->start,
1802                                 im->xlab_user.mgridtm,
1803                                 im->xlab_user.mgridst);
1804             ti < im->end; 
1805             ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1806             ){
1807             /* are we inside the graph ? */
1808             if (ti < im->start || ti > im->end) continue;
1809             while (timajor < ti) {
1810                 timajor = find_next_time(timajor,
1811                         im->xlab_user.mgridtm, im->xlab_user.mgridst);
1812             }
1813             if (ti == timajor) continue; /* skip as falls on major grid line */
1814            X0 = xtr(im,ti);       
1815            gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1816                im->graph_col[GRC_GRID],
1817                im->grid_dash_on, im->grid_dash_off);
1818            
1819         }
1820     }
1821
1822     /* paint the major grid */
1823     for(ti = find_first_time(im->start,
1824                             im->xlab_user.mgridtm,
1825                             im->xlab_user.mgridst);
1826         ti < im->end; 
1827         ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1828         ){
1829         /* are we inside the graph ? */
1830         if (ti < im->start || ti > im->end) continue;
1831        X0 = xtr(im,ti);
1832        gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1833            im->graph_col[GRC_MGRID],
1834            im->grid_dash_on, im->grid_dash_off);
1835        
1836     }
1837     /* paint the labels below the graph */
1838     for(ti = find_first_time(im->start - im->xlab_user.precis/2,
1839                             im->xlab_user.labtm,
1840                             im->xlab_user.labst);
1841         ti <= im->end - im->xlab_user.precis/2; 
1842         ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1843         ){
1844         tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1845         /* are we inside the graph ? */
1846         if (tilab < im->start || tilab > im->end) continue;
1847
1848 #if HAVE_STRFTIME
1849         localtime_r(&tilab, &tm);
1850         strftime(graph_label,99,im->xlab_user.stst, &tm);
1851 #else
1852 # error "your libc has no strftime I guess we'll abort the exercise here."
1853 #endif
1854        gfx_new_text ( im->canvas,
1855                       xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
1856                       im->graph_col[GRC_FONT],
1857                       im->text_prop[TEXT_PROP_AXIS].font,
1858                       im->text_prop[TEXT_PROP_AXIS].size,
1859                       im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
1860                       graph_label );
1861        
1862     }
1863
1864 }
1865
1866
1867 void 
1868 axis_paint(
1869    image_desc_t   *im
1870            )
1871 {   
1872     /* draw x and y axis */
1873     /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1874                       im->xorigin+im->xsize,im->yorigin-im->ysize,
1875                       GRIDWIDTH, im->graph_col[GRC_AXIS]);
1876        
1877        gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1878                          im->xorigin+im->xsize,im->yorigin-im->ysize,
1879                          GRIDWIDTH, im->graph_col[GRC_AXIS]); */
1880    
1881        gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1882                          im->xorigin+im->xsize+4,im->yorigin,
1883                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
1884    
1885        gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1886                          im->xorigin,im->yorigin-im->ysize-4,
1887                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
1888    
1889     
1890     /* arrow for X and Y axis direction */
1891     gfx_new_area ( im->canvas, 
1892                    im->xorigin+im->xsize+2,  im->yorigin-2,
1893                    im->xorigin+im->xsize+2,  im->yorigin+3,
1894                    im->xorigin+im->xsize+7,  im->yorigin+0.5, /* LINEOFFSET */
1895                    im->graph_col[GRC_ARROW]);
1896
1897     gfx_new_area ( im->canvas, 
1898                    im->xorigin-2,  im->yorigin-im->ysize-2,
1899                    im->xorigin+3,  im->yorigin-im->ysize-2,
1900                    im->xorigin+0.5,    im->yorigin-im->ysize-7, /* LINEOFFSET */
1901                    im->graph_col[GRC_ARROW]);
1902
1903 }
1904
1905 void
1906 grid_paint(image_desc_t   *im)
1907 {   
1908     long i;
1909     int res=0;
1910     double X0,Y0; /* points for filled graph and more*/
1911     gfx_node_t *node;
1912
1913     /* draw 3d border */
1914     node = gfx_new_area (im->canvas, 0,im->yimg,
1915                                  2,im->yimg-2,
1916                                  2,2,im->graph_col[GRC_SHADEA]);
1917     gfx_add_point( node , im->ximg - 2, 2 );
1918     gfx_add_point( node , im->ximg, 0 );
1919     gfx_add_point( node , 0,0 );
1920 /*    gfx_add_point( node , 0,im->yimg ); */
1921    
1922     node =  gfx_new_area (im->canvas, 2,im->yimg-2,
1923                                   im->ximg-2,im->yimg-2,
1924                                   im->ximg - 2, 2,
1925                                  im->graph_col[GRC_SHADEB]);
1926     gfx_add_point( node ,   im->ximg,0);
1927     gfx_add_point( node ,   im->ximg,im->yimg);
1928     gfx_add_point( node ,   0,im->yimg);
1929 /*    gfx_add_point( node , 0,im->yimg ); */
1930    
1931    
1932     if (im->draw_x_grid == 1 )
1933       vertical_grid(im);
1934     
1935     if (im->draw_y_grid == 1){
1936         if(im->logarithmic){
1937                 res = horizontal_log_grid(im);
1938         } else {
1939                 res = draw_horizontal_grid(im);
1940         }
1941         
1942         /* dont draw horizontal grid if there is no min and max val */
1943         if (! res ) {
1944           char *nodata = "No Data found";
1945            gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1946                         im->graph_col[GRC_FONT],
1947                         im->text_prop[TEXT_PROP_AXIS].font,
1948                         im->text_prop[TEXT_PROP_AXIS].size,
1949                         im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1950                         nodata );          
1951         }
1952     }
1953
1954     /* yaxis unit description */
1955     gfx_new_text( im->canvas,
1956                   10, (im->yorigin - im->ysize/2),
1957                   im->graph_col[GRC_FONT],
1958                   im->text_prop[TEXT_PROP_UNIT].font,
1959                   im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth, 
1960                   RRDGRAPH_YLEGEND_ANGLE,
1961                   GFX_H_LEFT, GFX_V_CENTER,
1962                   im->ylegend);
1963
1964     /* graph title */
1965     gfx_new_text( im->canvas,
1966                   im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
1967                   im->graph_col[GRC_FONT],
1968                   im->text_prop[TEXT_PROP_TITLE].font,
1969                   im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1970                   GFX_H_CENTER, GFX_V_CENTER,
1971                   im->title);
1972     /* rrdtool 'logo' */
1973     gfx_new_text( im->canvas,
1974                   im->ximg-7, 7,
1975                   ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
1976                   im->text_prop[TEXT_PROP_AXIS].font,
1977                   5.5, im->tabwidth, 270,
1978                   GFX_H_RIGHT, GFX_V_TOP,
1979                   "RRDTOOL / TOBI OETIKER");
1980
1981     /* graph watermark */
1982     if(im->watermark[0] != '\0') {
1983         gfx_new_text( im->canvas,
1984                   im->ximg/2, im->yimg-6,
1985                   ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
1986                   im->text_prop[TEXT_PROP_AXIS].font,
1987                   5.5, im->tabwidth, 0,
1988                   GFX_H_CENTER, GFX_V_BOTTOM,
1989                   im->watermark);
1990     }
1991     
1992     /* graph labels */
1993     if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1994             for(i=0;i<im->gdes_c;i++){
1995                     if(im->gdes[i].legend[0] =='\0')
1996                             continue;
1997                     
1998                     /* im->gdes[i].leg_y is the bottom of the legend */
1999                     X0 = im->gdes[i].leg_x;
2000                     Y0 = im->gdes[i].leg_y;
2001                     gfx_new_text ( im->canvas, X0, Y0,
2002                                    im->graph_col[GRC_FONT],
2003                                    im->text_prop[TEXT_PROP_LEGEND].font,
2004                                    im->text_prop[TEXT_PROP_LEGEND].size,
2005                                    im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2006                                    im->gdes[i].legend );
2007                     /* The legend for GRAPH items starts with "M " to have
2008                        enough space for the box */
2009                     if (           im->gdes[i].gf != GF_PRINT &&
2010                                    im->gdes[i].gf != GF_GPRINT &&
2011                                    im->gdes[i].gf != GF_COMMENT) {
2012                             int boxH, boxV;
2013                             
2014                             boxH = gfx_get_text_width(im->canvas, 0,
2015                                                       im->text_prop[TEXT_PROP_LEGEND].font,
2016                                                       im->text_prop[TEXT_PROP_LEGEND].size,
2017                                                       im->tabwidth,"o", 0) * 1.2;
2018                             boxV = boxH*1.1;
2019                             
2020                             /* make sure transparent colors show up the same way as in the graph */
2021                              node = gfx_new_area(im->canvas,
2022                                                 X0,Y0-boxV,
2023                                                 X0,Y0,
2024                                                 X0+boxH,Y0,
2025                                                 im->graph_col[GRC_BACK]);
2026                             gfx_add_point ( node, X0+boxH, Y0-boxV );
2027
2028                             node = gfx_new_area(im->canvas,
2029                                                 X0,Y0-boxV,
2030                                                 X0,Y0,
2031                                                 X0+boxH,Y0,
2032                                                 im->gdes[i].col);
2033                             gfx_add_point ( node, X0+boxH, Y0-boxV );
2034                             node = gfx_new_line(im->canvas,
2035                                                 X0,Y0-boxV,
2036                                                 X0,Y0,
2037                                                 1.0,im->graph_col[GRC_FRAME]);
2038                             gfx_add_point(node,X0+boxH,Y0);
2039                             gfx_add_point(node,X0+boxH,Y0-boxV);
2040                             gfx_close_path(node);
2041                     }
2042             }
2043     }
2044 }
2045
2046
2047 /*****************************************************
2048  * lazy check make sure we rely need to create this graph
2049  *****************************************************/
2050
2051 int lazy_check(image_desc_t *im){
2052     FILE *fd = NULL;
2053         int size = 1;
2054     struct stat  imgstat;
2055     
2056     if (im->lazy == 0) return 0; /* no lazy option */
2057     if (stat(im->graphfile,&imgstat) != 0) 
2058       return 0; /* can't stat */
2059     /* one pixel in the existing graph is more then what we would
2060        change here ... */
2061     if (time(NULL) - imgstat.st_mtime > 
2062         (im->end - im->start) / im->xsize) 
2063       return 0;
2064     if ((fd = fopen(im->graphfile,"rb")) == NULL) 
2065       return 0; /* the file does not exist */
2066     switch (im->canvas->imgformat) {
2067     case IF_PNG:
2068            size = PngSize(fd,&(im->ximg),&(im->yimg));
2069            break;
2070     default:
2071            size = 1;
2072     }
2073     fclose(fd);
2074     return size;
2075 }
2076
2077 #ifdef WITH_PIECHART
2078 void
2079 pie_part(image_desc_t *im, gfx_color_t color,
2080             double PieCenterX, double PieCenterY, double Radius,
2081             double startangle, double endangle)
2082 {
2083     gfx_node_t *node;
2084     double angle;
2085     double step=M_PI/50; /* Number of iterations for the circle;
2086                          ** 10 is definitely too low, more than
2087                          ** 50 seems to be overkill
2088                          */
2089
2090     /* Strange but true: we have to work clockwise or else
2091     ** anti aliasing nor transparency don't work.
2092     **
2093     ** This test is here to make sure we do it right, also
2094     ** this makes the for...next loop more easy to implement.
2095     ** The return will occur if the user enters a negative number
2096     ** (which shouldn't be done according to the specs) or if the
2097     ** programmers do something wrong (which, as we all know, never
2098     ** happens anyway :)
2099     */
2100     if (endangle<startangle) return;
2101
2102     /* Hidden feature: Radius decreases each full circle */
2103     angle=startangle;
2104     while (angle>=2*M_PI) {
2105         angle  -= 2*M_PI;
2106         Radius *= 0.8;
2107     }
2108
2109     node=gfx_new_area(im->canvas,
2110                 PieCenterX+sin(startangle)*Radius,
2111                 PieCenterY-cos(startangle)*Radius,
2112                 PieCenterX,
2113                 PieCenterY,
2114                 PieCenterX+sin(endangle)*Radius,
2115                 PieCenterY-cos(endangle)*Radius,
2116                 color);
2117     for (angle=endangle;angle-startangle>=step;angle-=step) {
2118         gfx_add_point(node,
2119                 PieCenterX+sin(angle)*Radius,
2120                 PieCenterY-cos(angle)*Radius );
2121     }
2122 }
2123
2124 #endif
2125
2126 int
2127 graph_size_location(image_desc_t *im, int elements
2128
2129 #ifdef WITH_PIECHART
2130 , int piechart
2131 #endif
2132
2133  )
2134 {
2135     /* The actual size of the image to draw is determined from
2136     ** several sources.  The size given on the command line is
2137     ** the graph area but we need more as we have to draw labels
2138     ** and other things outside the graph area
2139     */
2140
2141     /* +-+-------------------------------------------+
2142     ** |l|.................title.....................|
2143     ** |e+--+-------------------------------+--------+
2144     ** |b| b|                               |        |
2145     ** |a| a|                               |  pie   |
2146     ** |l| l|          main graph area      | chart  |
2147     ** |.| .|                               |  area  |
2148     ** |t| y|                               |        |
2149     ** |r+--+-------------------------------+--------+
2150     ** |e|  | x-axis labels                 |        |
2151     ** |v+--+-------------------------------+--------+
2152     ** | |..............legends......................|
2153     ** +-+-------------------------------------------+
2154     ** |                 watermark                   |
2155     ** +---------------------------------------------+
2156     */
2157     int Xvertical=0,    
2158                         Ytitle   =0,
2159         Xylabel  =0,    
2160         Xmain    =0,    Ymain    =0,
2161 #ifdef WITH_PIECHART
2162         Xpie     =0,    Ypie     =0,
2163 #endif
2164                         Yxlabel  =0,
2165 #if 0
2166         Xlegend  =0,    Ylegend  =0,
2167 #endif
2168         Xspacing =15,  Yspacing =15,
2169        
2170                       Ywatermark =4;
2171
2172     if (im->extra_flags & ONLY_GRAPH) {
2173         im->xorigin =0;
2174         im->ximg = im->xsize;
2175         im->yimg = im->ysize;
2176         im->yorigin = im->ysize;
2177         ytr(im,DNAN); 
2178         return 0;
2179     }
2180
2181     if (im->ylegend[0] != '\0' ) {
2182            Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2183     }
2184
2185
2186     if (im->title[0] != '\0') {
2187         /* The title is placed "inbetween" two text lines so it
2188         ** automatically has some vertical spacing.  The horizontal
2189         ** spacing is added here, on each side.
2190         */
2191         /* don't care for the with of the title
2192                 Xtitle = gfx_get_text_width(im->canvas, 0,
2193                 im->text_prop[TEXT_PROP_TITLE].font,
2194                 im->text_prop[TEXT_PROP_TITLE].size,
2195                 im->tabwidth,
2196                 im->title, 0) + 2*Xspacing; */
2197         Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2198     }
2199
2200     if (elements) {
2201         Xmain=im->xsize;
2202         Ymain=im->ysize;
2203         if (im->draw_x_grid) {
2204             Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2205         }
2206         if (im->draw_y_grid) {
2207             Xylabel=gfx_get_text_width(im->canvas, 0,
2208                         im->text_prop[TEXT_PROP_AXIS].font,
2209                         im->text_prop[TEXT_PROP_AXIS].size,
2210                         im->tabwidth,
2211                         "0", 0) * im->unitslength;
2212         }
2213     }
2214
2215 #ifdef WITH_PIECHART
2216     if (piechart) {
2217         im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2218         Xpie=im->piesize;
2219         Ypie=im->piesize;
2220     }
2221 #endif
2222
2223     /* Now calculate the total size.  Insert some spacing where
2224        desired.  im->xorigin and im->yorigin need to correspond
2225        with the lower left corner of the main graph area or, if
2226        this one is not set, the imaginary box surrounding the
2227        pie chart area. */
2228
2229     /* The legend width cannot yet be determined, as a result we
2230     ** have problems adjusting the image to it.  For now, we just
2231     ** forget about it at all; the legend will have to fit in the
2232     ** size already allocated.
2233     */
2234     im->ximg = Xylabel + Xmain + 2 * Xspacing;
2235
2236 #ifdef WITH_PIECHART
2237     im->ximg  += Xpie;
2238 #endif
2239
2240     if (Xmain) im->ximg += Xspacing;
2241 #ifdef WITH_PIECHART
2242     if (Xpie) im->ximg += Xspacing;
2243 #endif
2244
2245     im->xorigin = Xspacing + Xylabel;
2246
2247     /* the length of the title should not influence with width of the graph
2248        if (Xtitle > im->ximg) im->ximg = Xtitle; */
2249
2250     if (Xvertical) { /* unit description */
2251         im->ximg += Xvertical;
2252         im->xorigin += Xvertical;
2253     }
2254     xtr(im,0);
2255
2256     /* The vertical size is interesting... we need to compare
2257     ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with 
2258     ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2259     ** in order to start even thinking about Ylegend or Ywatermark.
2260     **
2261     ** Do it in three portions: First calculate the inner part,
2262     ** then do the legend, then adjust the total height of the img,
2263     ** adding space for a watermark if one exists;
2264     */
2265
2266     /* reserve space for main and/or pie */
2267
2268     im->yimg = Ymain + Yxlabel;
2269     
2270 #ifdef WITH_PIECHART
2271     if (im->yimg < Ypie) im->yimg = Ypie;
2272 #endif
2273
2274     im->yorigin = im->yimg - Yxlabel;
2275
2276     /* reserve space for the title *or* some padding above the graph */
2277     if (Ytitle) {
2278         im->yimg += Ytitle;
2279         im->yorigin += Ytitle;
2280     } else {
2281         im->yimg += 1.5*Yspacing;
2282         im->yorigin += 1.5*Yspacing;
2283     }
2284     /* reserve space for padding below the graph */
2285     im->yimg += Yspacing;
2286      
2287     /* Determine where to place the legends onto the image.
2288     ** Adjust im->yimg to match the space requirements.
2289     */
2290     if(leg_place(im)==-1)
2291         return -1;
2292         
2293     if (im->watermark[0] != '\0') {
2294         im->yimg += Ywatermark;
2295     }
2296
2297 #if 0
2298     if (Xlegend > im->ximg) {
2299         im->ximg = Xlegend;
2300         /* reposition Pie */
2301     }
2302 #endif
2303
2304 #ifdef WITH_PIECHART
2305     /* The pie is placed in the upper right hand corner,
2306     ** just below the title (if any) and with sufficient
2307     ** padding.
2308     */
2309     if (elements) {
2310         im->pie_x = im->ximg - Xspacing - Xpie/2;
2311         im->pie_y = im->yorigin-Ymain+Ypie/2;
2312     } else {
2313         im->pie_x = im->ximg/2;
2314         im->pie_y = im->yorigin-Ypie/2;
2315     }
2316 #endif
2317
2318     ytr(im,DNAN);
2319     return 0;
2320 }
2321
2322 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2323 /* yes we are loosing precision by doing tos with floats instead of doubles
2324    but it seems more stable this way. */
2325    
2326 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
2327 {
2328
2329     int aInt = *(int*)&A;
2330     int bInt = *(int*)&B;
2331     int intDiff;
2332     /* Make sure maxUlps is non-negative and small enough that the
2333        default NAN won't compare as equal to anything.  */
2334
2335     /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
2336
2337     /* Make aInt lexicographically ordered as a twos-complement int */
2338
2339     if (aInt < 0)
2340         aInt = 0x80000000l - aInt;
2341
2342     /* Make bInt lexicographically ordered as a twos-complement int */
2343
2344     if (bInt < 0)
2345         bInt = 0x80000000l - bInt;
2346
2347     intDiff = abs(aInt - bInt);
2348
2349     if (intDiff <= maxUlps)
2350         return 1;
2351
2352     return 0;
2353 }
2354
2355 /* draw that picture thing ... */
2356 int
2357 graph_paint(image_desc_t *im, char ***calcpr)
2358 {
2359   int i,ii;
2360   int lazy =     lazy_check(im);
2361 #ifdef WITH_PIECHART
2362   int piechart = 0;
2363   double PieStart=0.0;
2364 #endif
2365   FILE  *fo;
2366   gfx_node_t *node;
2367   
2368   double areazero = 0.0;
2369   graph_desc_t *lastgdes = NULL;    
2370
2371   /* if we are lazy and there is nothing to PRINT ... quit now */
2372   if (lazy && im->prt_c==0) return 0;
2373
2374   /* pull the data from the rrd files ... */
2375   
2376   if(data_fetch(im)==-1)
2377     return -1;
2378
2379   /* evaluate VDEF and CDEF operations ... */
2380   if(data_calc(im)==-1)
2381     return -1;
2382
2383 #ifdef WITH_PIECHART  
2384   /* check if we need to draw a piechart */
2385   for(i=0;i<im->gdes_c;i++){
2386     if (im->gdes[i].gf == GF_PART) {
2387       piechart=1;
2388       break;
2389     }
2390   }
2391 #endif
2392
2393   /* calculate and PRINT and GPRINT definitions. We have to do it at
2394    * this point because it will affect the length of the legends
2395    * if there are no graph elements we stop here ... 
2396    * if we are lazy, try to quit ... 
2397    */
2398   i=print_calc(im,calcpr);
2399   if(i<0) return -1;
2400   if(((i==0)
2401 #ifdef WITH_PIECHART
2402 &&(piechart==0)
2403 #endif
2404 ) || lazy) return 0;
2405
2406 #ifdef WITH_PIECHART
2407   /* If there's only the pie chart to draw, signal this */
2408   if (i==0) piechart=2;
2409 #endif
2410   
2411   /* get actual drawing data and find min and max values*/
2412   if(data_proc(im)==-1)
2413     return -1;
2414   
2415   if(!im->logarithmic){si_unit(im);}        /* identify si magnitude Kilo, Mega Giga ? */
2416   
2417   if(!im->rigid && ! im->logarithmic)
2418     expand_range(im);   /* make sure the upper and lower limit are
2419                            sensible values */
2420
2421   if (!calc_horizontal_grid(im))
2422     return -1;
2423
2424   if (im->gridfit)
2425     apply_gridfit(im);
2426
2427
2428 /**************************************************************
2429  *** Calculating sizes and locations became a bit confusing ***
2430  *** so I moved this into a separate function.              ***
2431  **************************************************************/
2432   if(graph_size_location(im,i
2433 #ifdef WITH_PIECHART
2434 ,piechart
2435 #endif
2436 )==-1)
2437     return -1;
2438
2439   /* the actual graph is created by going through the individual
2440      graph elements and then drawing them */
2441   
2442   node=gfx_new_area ( im->canvas,
2443                       0, 0,
2444                       0, im->yimg,
2445                       im->ximg, im->yimg,                      
2446                       im->graph_col[GRC_BACK]);
2447
2448   gfx_add_point(node,im->ximg, 0);
2449
2450 #ifdef WITH_PIECHART
2451   if (piechart != 2) {
2452 #endif
2453     node=gfx_new_area ( im->canvas,
2454                       im->xorigin,             im->yorigin, 
2455                       im->xorigin + im->xsize, im->yorigin,
2456                       im->xorigin + im->xsize, im->yorigin-im->ysize,
2457                       im->graph_col[GRC_CANVAS]);
2458   
2459     gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2460
2461     if (im->minval > 0.0)
2462       areazero = im->minval;
2463     if (im->maxval < 0.0)
2464       areazero = im->maxval;
2465 #ifdef WITH_PIECHART
2466    }
2467 #endif
2468
2469 #ifdef WITH_PIECHART
2470   if (piechart) {
2471     pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2472   }
2473 #endif
2474
2475   for(i=0;i<im->gdes_c;i++){
2476     switch(im->gdes[i].gf){
2477     case GF_CDEF:
2478     case GF_VDEF:
2479     case GF_DEF:
2480     case GF_PRINT:
2481     case GF_GPRINT:
2482     case GF_COMMENT:
2483     case GF_HRULE:
2484     case GF_VRULE:
2485     case GF_XPORT:
2486     case GF_SHIFT:
2487       break;
2488     case GF_TICK:
2489       for (ii = 0; ii < im->xsize; ii++)
2490         {
2491           if (!isnan(im->gdes[i].p_data[ii]) && 
2492               im->gdes[i].p_data[ii] != 0.0)
2493            { 
2494               if (im -> gdes[i].yrule > 0 ) {
2495                       gfx_new_line(im->canvas,
2496                                    im -> xorigin + ii, im->yorigin,
2497                                    im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2498                                    1.0,
2499                                    im -> gdes[i].col );
2500               } else if ( im -> gdes[i].yrule < 0 ) {
2501                       gfx_new_line(im->canvas,
2502                                    im -> xorigin + ii, im->yorigin - im -> ysize,
2503                                    im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2504                                    1.0,
2505                                    im -> gdes[i].col );
2506               
2507               }
2508            }
2509         }
2510       break;
2511     case GF_LINE:
2512     case GF_AREA:
2513       /* fix data points at oo and -oo */
2514       for(ii=0;ii<im->xsize;ii++){
2515         if (isinf(im->gdes[i].p_data[ii])){
2516           if (im->gdes[i].p_data[ii] > 0) {
2517             im->gdes[i].p_data[ii] = im->maxval ;
2518           } else {
2519             im->gdes[i].p_data[ii] = im->minval ;
2520           }                 
2521           
2522         }
2523       } /* for */
2524
2525       /* *******************************************************
2526        a           ___. (a,t) 
2527                   |   |    ___
2528               ____|   |   |   |
2529               |       |___|
2530        -------|--t-1--t--------------------------------      
2531                       
2532       if we know the value at time t was a then 
2533       we draw a square from t-1 to t with the value a.
2534
2535       ********************************************************* */
2536       if (im->gdes[i].col != 0x0){   
2537         /* GF_LINE and friend */
2538         if(im->gdes[i].gf == GF_LINE ){
2539           double last_y=0.0;
2540           node = NULL;
2541           for(ii=1;ii<im->xsize;ii++){
2542             if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2543                 node = NULL;
2544                 continue;
2545             }
2546             if ( node == NULL ) {
2547                 last_y = ytr(im,im->gdes[i].p_data[ii]);
2548                 if ( im->slopemode == 0 ){
2549                   node = gfx_new_line(im->canvas,
2550                                     ii-1+im->xorigin,last_y,
2551                                     ii+im->xorigin,last_y,
2552                                     im->gdes[i].linewidth,
2553                                     im->gdes[i].col);
2554                 } else {
2555                   node = gfx_new_line(im->canvas,
2556                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2557                                     ii+im->xorigin,last_y,
2558                                     im->gdes[i].linewidth,
2559                                     im->gdes[i].col);
2560                 }
2561              } else {
2562                double new_y = ytr(im,im->gdes[i].p_data[ii]);
2563                if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2564                    gfx_add_point(node,ii-1+im->xorigin,new_y);
2565                };
2566                last_y = new_y;
2567                gfx_add_point(node,ii+im->xorigin,new_y);
2568              };
2569
2570           }
2571         } else {
2572           int idxI=-1;
2573           double *foreY=malloc(sizeof(double)*im->xsize*2);
2574           double *foreX=malloc(sizeof(double)*im->xsize*2);
2575           double *backY=malloc(sizeof(double)*im->xsize*2);
2576           double *backX=malloc(sizeof(double)*im->xsize*2);
2577           int drawem = 0;
2578           for(ii=0;ii<=im->xsize;ii++){
2579             double ybase,ytop;
2580             if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2581                int cntI=1;
2582                int lastI=0;
2583                while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2584                node = gfx_new_area(im->canvas,
2585                                 backX[0],backY[0],
2586                                 foreX[0],foreY[0],
2587                                 foreX[cntI],foreY[cntI], im->gdes[i].col);
2588                while (cntI < idxI) {
2589                  lastI = cntI;
2590                  cntI++;
2591                  while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;} 
2592                  gfx_add_point(node,foreX[cntI],foreY[cntI]);
2593                }
2594                gfx_add_point(node,backX[idxI],backY[idxI]);
2595                while (idxI > 1){
2596                  lastI = idxI;
2597                  idxI--;
2598                  while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;} 
2599                  gfx_add_point(node,backX[idxI],backY[idxI]);
2600                }
2601                idxI=-1;
2602                drawem = 0;
2603             }
2604             if (drawem != 0){
2605               drawem = 0;
2606               idxI=-1;
2607             }
2608             if (ii == im->xsize) break;
2609             
2610             /* keep things simple for now, just draw these bars
2611                do not try to build a big and complex area */
2612
2613                                                               
2614             if ( im->slopemode == 0 && ii==0){
2615                 continue;
2616             }
2617             if ( isnan(im->gdes[i].p_data[ii]) ) {
2618                 drawem = 1;
2619                 continue;
2620             }
2621             ytop = ytr(im,im->gdes[i].p_data[ii]);
2622             if ( lastgdes && im->gdes[i].stack ) {
2623                   ybase = ytr(im,lastgdes->p_data[ii]);
2624             } else {
2625                   ybase = ytr(im,areazero);
2626             }
2627             if ( ybase == ytop ){
2628                 drawem = 1;
2629                 continue;       
2630             }
2631             /* every area has to be wound clock-wise,
2632                so we have to make sur base remains base  */             
2633             if (ybase > ytop){
2634                 double extra = ytop;
2635                 ytop = ybase;
2636                 ybase = extra;
2637             }
2638             if ( im->slopemode == 0 ){
2639                     backY[++idxI] = ybase-0.2;
2640                     backX[idxI] = ii+im->xorigin-1;
2641                     foreY[idxI] = ytop+0.2;
2642                     foreX[idxI] = ii+im->xorigin-1;
2643             }
2644             backY[++idxI] = ybase-0.2;
2645             backX[idxI] = ii+im->xorigin;
2646             foreY[idxI] = ytop+0.2;
2647             foreX[idxI] = ii+im->xorigin;
2648           }
2649           /* close up any remaining area */             
2650           free(foreY);
2651           free(foreX);
2652           free(backY);
2653           free(backX);
2654         } /* else GF_LINE */
2655       } /* if color != 0x0 */
2656       /* make sure we do not run into trouble when stacking on NaN */
2657       for(ii=0;ii<im->xsize;ii++){
2658         if (isnan(im->gdes[i].p_data[ii])) {
2659           if (lastgdes && (im->gdes[i].stack)) {
2660             im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2661           } else {
2662             im->gdes[i].p_data[ii] = areazero;
2663           }
2664         }
2665       } 
2666       lastgdes = &(im->gdes[i]);                         
2667       break;
2668 #ifdef WITH_PIECHART
2669     case GF_PART:
2670       if(isnan(im->gdes[i].yrule)) /* fetch variable */
2671         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2672      
2673       if (finite(im->gdes[i].yrule)) {  /* even the fetched var can be NaN */
2674         pie_part(im,im->gdes[i].col,
2675                 im->pie_x,im->pie_y,im->piesize*0.4,
2676                 M_PI*2.0*PieStart/100.0,
2677                 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2678         PieStart += im->gdes[i].yrule;
2679       }
2680       break;
2681 #endif
2682     case GF_STACK:
2683       rrd_set_error("STACK should already be turned into LINE or AREA here");
2684       return -1;
2685       break;
2686         
2687     } /* switch */
2688   }
2689 #ifdef WITH_PIECHART
2690   if (piechart==2) {
2691     im->draw_x_grid=0;
2692     im->draw_y_grid=0;
2693   }
2694 #endif
2695
2696
2697   /* grid_paint also does the text */
2698   if( !(im->extra_flags & ONLY_GRAPH) )  
2699     grid_paint(im);
2700
2701   
2702   if( !(im->extra_flags & ONLY_GRAPH) )  
2703       axis_paint(im);
2704   
2705   /* the RULES are the last thing to paint ... */
2706   for(i=0;i<im->gdes_c;i++){    
2707     
2708     switch(im->gdes[i].gf){
2709     case GF_HRULE:
2710       if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2711         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2712       };
2713       if(im->gdes[i].yrule >= im->minval
2714          && im->gdes[i].yrule <= im->maxval)
2715         gfx_new_line(im->canvas,
2716                      im->xorigin,ytr(im,im->gdes[i].yrule),
2717                      im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2718                      1.0,im->gdes[i].col); 
2719       break;
2720     case GF_VRULE:
2721       if(im->gdes[i].xrule == 0) { /* fetch variable */
2722         im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2723       };
2724       if(im->gdes[i].xrule >= im->start
2725          && im->gdes[i].xrule <= im->end)
2726         gfx_new_line(im->canvas,
2727                      xtr(im,im->gdes[i].xrule),im->yorigin,
2728                      xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2729                      1.0,im->gdes[i].col); 
2730       break;
2731     default:
2732       break;
2733     }
2734   }
2735
2736   
2737   if (strcmp(im->graphfile,"-")==0) {
2738     fo = im->graphhandle ? im->graphhandle : stdout;
2739 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2740     /* Change translation mode for stdout to BINARY */
2741     _setmode( _fileno( fo ), O_BINARY );
2742 #endif
2743   } else {
2744     if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2745       rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2746                     rrd_strerror(errno));
2747       return (-1);
2748     }
2749   }
2750   gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2751   if (strcmp(im->graphfile,"-") != 0)
2752     fclose(fo);
2753   return 0;
2754 }
2755
2756
2757 /*****************************************************
2758  * graph stuff 
2759  *****************************************************/
2760
2761 int
2762 gdes_alloc(image_desc_t *im){
2763
2764     im->gdes_c++;
2765     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2766                                            * sizeof(graph_desc_t)))==NULL){
2767         rrd_set_error("realloc graph_descs");
2768         return -1;
2769     }
2770
2771
2772     im->gdes[im->gdes_c-1].step=im->step;
2773     im->gdes[im->gdes_c-1].step_orig=im->step;
2774     im->gdes[im->gdes_c-1].stack=0;
2775     im->gdes[im->gdes_c-1].linewidth=0;
2776     im->gdes[im->gdes_c-1].debug=0;
2777     im->gdes[im->gdes_c-1].start=im->start; 
2778     im->gdes[im->gdes_c-1].end=im->end; 
2779     im->gdes[im->gdes_c-1].vname[0]='\0'; 
2780     im->gdes[im->gdes_c-1].data=NULL;
2781     im->gdes[im->gdes_c-1].ds_namv=NULL;
2782     im->gdes[im->gdes_c-1].data_first=0;
2783     im->gdes[im->gdes_c-1].p_data=NULL;
2784     im->gdes[im->gdes_c-1].rpnp=NULL;
2785     im->gdes[im->gdes_c-1].shift=0;
2786     im->gdes[im->gdes_c-1].col = 0x0;
2787     im->gdes[im->gdes_c-1].legend[0]='\0';
2788     im->gdes[im->gdes_c-1].format[0]='\0';
2789     im->gdes[im->gdes_c-1].rrd[0]='\0';
2790     im->gdes[im->gdes_c-1].ds=-1;    
2791     im->gdes[im->gdes_c-1].p_data=NULL;    
2792     im->gdes[im->gdes_c-1].yrule=DNAN;
2793     im->gdes[im->gdes_c-1].xrule=0;
2794     return 0;
2795 }
2796
2797 /* copies input untill the first unescaped colon is found
2798    or until input ends. backslashes have to be escaped as well */
2799 int
2800 scan_for_col(const char *const input, int len, char *const output)
2801 {
2802     int inp,outp=0;
2803     for (inp=0; 
2804          inp < len &&
2805            input[inp] != ':' &&
2806            input[inp] != '\0';
2807          inp++){
2808       if (input[inp] == '\\' &&
2809           input[inp+1] != '\0' && 
2810           (input[inp+1] == '\\' ||
2811            input[inp+1] == ':')){
2812         output[outp++] = input[++inp];
2813       }
2814       else {
2815         output[outp++] = input[inp];
2816       }
2817     }
2818     output[outp] = '\0';
2819     return inp;
2820 }
2821 /* Some surgery done on this function, it became ridiculously big.
2822 ** Things moved:
2823 ** - initializing     now in rrd_graph_init()
2824 ** - options parsing  now in rrd_graph_options()
2825 ** - script parsing   now in rrd_graph_script()
2826 */
2827 int 
2828 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
2829 {
2830     image_desc_t   im;
2831     rrd_graph_init(&im);
2832     im.graphhandle = stream;
2833     
2834     rrd_graph_options(argc,argv,&im);
2835     if (rrd_test_error()) {
2836         im_free(&im);
2837         return -1;
2838     }
2839     
2840     if (strlen(argv[optind])>=MAXPATH) {
2841         rrd_set_error("filename (including path) too long");
2842         im_free(&im);
2843         return -1;
2844     }
2845     strncpy(im.graphfile,argv[optind],MAXPATH-1);
2846     im.graphfile[MAXPATH-1]='\0';
2847
2848     rrd_graph_script(argc,argv,&im,1);
2849     if (rrd_test_error()) {
2850         im_free(&im);
2851         return -1;
2852     }
2853
2854     /* Everything is now read and the actual work can start */
2855
2856     (*prdata)=NULL;
2857     if (graph_paint(&im,prdata)==-1){
2858         im_free(&im);
2859         return -1;
2860     }
2861
2862     /* The image is generated and needs to be output.
2863     ** Also, if needed, print a line with information about the image.
2864     */
2865
2866     *xsize=im.ximg;
2867     *ysize=im.yimg;
2868     *ymin=im.minval;
2869     *ymax=im.maxval;
2870     if (im.imginfo) {
2871         char *filename;
2872         if (!(*prdata)) {
2873             /* maybe prdata is not allocated yet ... lets do it now */
2874             if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2875                 rrd_set_error("malloc imginfo");
2876                 return -1; 
2877             };
2878         }
2879         if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2880          ==NULL){
2881             rrd_set_error("malloc imginfo");
2882             return -1;
2883         }
2884         filename=im.graphfile+strlen(im.graphfile);
2885         while(filename > im.graphfile) {
2886             if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2887             filename--;
2888         }
2889
2890         sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2891     }
2892     im_free(&im);
2893     return 0;
2894 }
2895
2896 void
2897 rrd_graph_init(image_desc_t *im)
2898 {
2899     unsigned int i;
2900
2901 #ifdef HAVE_TZSET
2902     tzset();
2903 #endif
2904 #ifdef HAVE_SETLOCALE
2905     setlocale(LC_TIME,"");
2906 #ifdef HAVE_MBSTOWCS
2907     setlocale(LC_CTYPE,"");
2908 #endif
2909 #endif
2910     im->yorigin=0;
2911     im->xorigin=0;
2912     im->minval=0;
2913     im->xlab_user.minsec = -1;
2914     im->ximg=0;
2915     im->yimg=0;
2916     im->xsize = 400;
2917     im->ysize = 100;
2918     im->step = 0;
2919     im->ylegend[0] = '\0';
2920     im->title[0] = '\0';
2921     im->watermark[0] = '\0';
2922     im->minval = DNAN;
2923     im->maxval = DNAN;    
2924     im->unitsexponent= 9999;
2925     im->unitslength= 6; 
2926     im->symbol = ' ';
2927     im->viewfactor = 1.0;
2928     im->extra_flags= 0;
2929     im->rigid = 0;
2930     im->gridfit = 1;
2931     im->imginfo = NULL;
2932     im->lazy = 0;
2933     im->slopemode = 0;
2934     im->logarithmic = 0;
2935     im->ygridstep = DNAN;
2936     im->draw_x_grid = 1;
2937     im->draw_y_grid = 1;
2938     im->base = 1000;
2939     im->prt_c = 0;
2940     im->gdes_c = 0;
2941     im->gdes = NULL;
2942     im->canvas = gfx_new_canvas();
2943     im->grid_dash_on = 1;
2944     im->grid_dash_off = 1;
2945     im->tabwidth = 40.0;
2946     
2947     for(i=0;i<DIM(graph_col);i++)
2948         im->graph_col[i]=graph_col[i];
2949
2950 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2951     {
2952             char *windir; 
2953             char rrd_win_default_font[1000];
2954             windir = getenv("windir");
2955             /* %windir% is something like D:\windows or C:\winnt */
2956             if (windir != NULL) {
2957                     strncpy(rrd_win_default_font,windir,500);
2958                     rrd_win_default_font[500] = '\0';
2959                     strcat(rrd_win_default_font,"\\fonts\\");
2960                     strcat(rrd_win_default_font,RRD_DEFAULT_FONT);         
2961                     for(i=0;i<DIM(text_prop);i++){
2962                             strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
2963                             text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
2964                      }
2965              }
2966     }
2967 #endif
2968     {
2969             char *deffont; 
2970             deffont = getenv("RRD_DEFAULT_FONT");
2971             if (deffont != NULL) {
2972                  for(i=0;i<DIM(text_prop);i++){
2973                         strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
2974                         text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
2975                  }
2976             }
2977     }
2978     for(i=0;i<DIM(text_prop);i++){        
2979       im->text_prop[i].size = text_prop[i].size;
2980       strcpy(im->text_prop[i].font,text_prop[i].font);
2981     }
2982 }
2983
2984 void
2985 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2986 {
2987     int                 stroff;    
2988     char                *parsetime_error = NULL;
2989     char                scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2990     time_t              start_tmp=0,end_tmp=0;
2991     long                long_tmp;
2992     struct rrd_time_value       start_tv, end_tv;
2993     gfx_color_t         color;
2994     optind = 0; opterr = 0;  /* initialize getopt */
2995
2996     parsetime("end-24h", &start_tv);
2997     parsetime("now", &end_tv);
2998
2999     while (1){
3000         static struct option long_options[] =
3001         {
3002             {"start",      required_argument, 0,  's'},
3003             {"end",        required_argument, 0,  'e'},
3004             {"x-grid",     required_argument, 0,  'x'},
3005             {"y-grid",     required_argument, 0,  'y'},
3006             {"vertical-label",required_argument,0,'v'},
3007             {"width",      required_argument, 0,  'w'},
3008             {"height",     required_argument, 0,  'h'},
3009             {"interlaced", no_argument,       0,  'i'},
3010             {"upper-limit",required_argument, 0,  'u'},
3011             {"lower-limit",required_argument, 0,  'l'},
3012             {"rigid",      no_argument,       0,  'r'},
3013             {"base",       required_argument, 0,  'b'},
3014             {"logarithmic",no_argument,       0,  'o'},
3015             {"color",      required_argument, 0,  'c'},
3016             {"font",       required_argument, 0,  'n'},
3017             {"title",      required_argument, 0,  't'},
3018             {"imginfo",    required_argument, 0,  'f'},
3019             {"imgformat",  required_argument, 0,  'a'},
3020             {"lazy",       no_argument,       0,  'z'},
3021             {"zoom",       required_argument, 0,  'm'},
3022             {"no-legend",  no_argument,       0,  'g'},
3023             {"force-rules-legend",no_argument,0,  'F'},
3024             {"only-graph", no_argument,       0,  'j'},
3025             {"alt-y-grid", no_argument,       0,  'Y'},
3026             {"no-minor",   no_argument,       0,  'I'},
3027             {"slope-mode", no_argument,       0,  'E'},
3028             {"alt-autoscale", no_argument,    0,  'A'},
3029             {"alt-autoscale-max", no_argument, 0, 'M'},
3030             {"no-gridfit", no_argument,       0,   'N'},
3031             {"units-exponent",required_argument, 0, 'X'},
3032             {"units-length",required_argument, 0, 'L'},
3033             {"step",       required_argument, 0,    'S'},
3034             {"tabwidth",   required_argument, 0,    'T'},            
3035             {"font-render-mode", required_argument, 0, 'R'},
3036             {"font-smoothing-threshold", required_argument, 0, 'B'},
3037             {"watermark",  required_argument, 0,  'W'},
3038             {"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 */
3039             {0,0,0,0}};
3040         int option_index = 0;
3041         int opt;
3042         int col_start,col_end;
3043
3044         opt = getopt_long(argc, argv, 
3045                          "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3046                           long_options, &option_index);
3047
3048         if (opt == EOF)
3049             break;
3050         
3051         switch(opt) {
3052         case 'I':
3053             im->extra_flags |= NOMINOR;
3054             break;
3055         case 'Y':
3056             im->extra_flags |= ALTYGRID;
3057             break;
3058         case 'A':
3059             im->extra_flags |= ALTAUTOSCALE;
3060             break;
3061         case 'M':
3062             im->extra_flags |= ALTAUTOSCALE_MAX;
3063             break;
3064         case 'j':
3065            im->extra_flags |= ONLY_GRAPH;
3066            break;
3067         case 'g':
3068             im->extra_flags |= NOLEGEND;
3069             break;
3070         case 'F':
3071             im->extra_flags |= FORCE_RULES_LEGEND;
3072             break;
3073         case 'X':
3074             im->unitsexponent = atoi(optarg);
3075             break;
3076         case 'L':
3077             im->unitslength = atoi(optarg);
3078             break;
3079         case 'T':
3080             im->tabwidth = atof(optarg);
3081             break;
3082         case 'S':
3083             im->step =  atoi(optarg);
3084             break;
3085         case 'N':
3086             im->gridfit = 0;
3087             break;
3088         case 's':
3089             if ((parsetime_error = parsetime(optarg, &start_tv))) {
3090                 rrd_set_error( "start time: %s", parsetime_error );
3091                 return;
3092             }
3093             break;
3094         case 'e':
3095             if ((parsetime_error = parsetime(optarg, &end_tv))) {
3096                 rrd_set_error( "end time: %s", parsetime_error );
3097                 return;
3098             }
3099             break;
3100         case 'x':
3101             if(strcmp(optarg,"none") == 0){
3102               im->draw_x_grid=0;
3103               break;
3104             };
3105                 
3106             if(sscanf(optarg,
3107                       "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3108                       scan_gtm,
3109                       &im->xlab_user.gridst,
3110                       scan_mtm,
3111                       &im->xlab_user.mgridst,
3112                       scan_ltm,
3113                       &im->xlab_user.labst,
3114                       &im->xlab_user.precis,
3115                       &stroff) == 7 && stroff != 0){
3116                 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3117                 im->xlab_form[sizeof(im->xlab_form)-1] = '\0'; 
3118                 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3119                     rrd_set_error("unknown keyword %s",scan_gtm);
3120                     return;
3121                 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3122                     rrd_set_error("unknown keyword %s",scan_mtm);
3123                     return;
3124                 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3125                     rrd_set_error("unknown keyword %s",scan_ltm);
3126                     return;
3127                 } 
3128                 im->xlab_user.minsec = 1;
3129                 im->xlab_user.stst = im->xlab_form;
3130             } else {
3131                 rrd_set_error("invalid x-grid format");
3132                 return;
3133             }
3134             break;
3135         case 'y':
3136
3137             if(strcmp(optarg,"none") == 0){
3138               im->draw_y_grid=0;
3139               break;
3140             };
3141
3142             if(sscanf(optarg,
3143                       "%lf:%d",
3144                       &im->ygridstep,
3145                       &im->ylabfact) == 2) {
3146                 if(im->ygridstep<=0){
3147                     rrd_set_error("grid step must be > 0");
3148                     return;
3149                 } else if (im->ylabfact < 1){
3150                     rrd_set_error("label factor must be > 0");
3151                     return;
3152                 } 
3153             } else {
3154                 rrd_set_error("invalid y-grid format");
3155                 return;
3156             }
3157             break;
3158         case 'v':
3159             strncpy(im->ylegend,optarg,150);
3160             im->ylegend[150]='\0';
3161             break;
3162         case 'u':
3163             im->maxval = atof(optarg);
3164             break;
3165         case 'l':
3166             im->minval = atof(optarg);
3167             break;
3168         case 'b':
3169             im->base = atol(optarg);
3170             if(im->base != 1024 && im->base != 1000 ){
3171                 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3172                 return;
3173             }
3174             break;
3175         case 'w':
3176             long_tmp = atol(optarg);
3177             if (long_tmp < 10) {
3178                 rrd_set_error("width below 10 pixels");
3179                 return;
3180             }
3181             im->xsize = long_tmp;
3182             break;
3183         case 'h':
3184             long_tmp = atol(optarg);
3185             if (long_tmp < 10) {
3186                 rrd_set_error("height below 10 pixels");
3187                 return;
3188             }
3189             im->ysize = long_tmp;
3190             break;
3191         case 'i':
3192             im->canvas->interlaced = 1;
3193             break;
3194         case 'r':
3195             im->rigid = 1;
3196             break;
3197         case 'f':
3198             im->imginfo = optarg;
3199             break;
3200         case 'a':
3201             if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3202                 rrd_set_error("unsupported graphics format '%s'",optarg);
3203                 return;
3204             }
3205             break;
3206         case 'z':
3207             im->lazy = 1;
3208             break;
3209         case 'E':
3210             im->slopemode = 1;
3211             break;
3212
3213         case 'o':
3214             im->logarithmic = 1;
3215             if (isnan(im->minval))
3216                 im->minval=1;
3217             break;
3218         case 'c':
3219             if(sscanf(optarg,
3220                       "%10[A-Z]#%n%8lx%n",
3221                       col_nam,&col_start,&color,&col_end) == 2){
3222                 int ci;
3223                 int col_len = col_end - col_start;
3224                 switch (col_len){
3225                         case 3:
3226                                 color = (
3227                                         ((color & 0xF00) * 0x110000) |
3228                                         ((color & 0x0F0) * 0x011000) |
3229                                         ((color & 0x00F) * 0x001100) |
3230                                         0x000000FF
3231                                         );
3232                                 break;
3233                         case 4:
3234                                 color = (
3235                                         ((color & 0xF000) * 0x11000) |
3236                                         ((color & 0x0F00) * 0x01100) |
3237                                         ((color & 0x00F0) * 0x00110) |
3238                                         ((color & 0x000F) * 0x00011)
3239                                         );
3240                                 break;
3241                         case 6:
3242                                 color = (color << 8) + 0xff /* shift left by 8 */;
3243                                 break;
3244                         case 8:
3245                                 break;
3246                         default:
3247                                 rrd_set_error("the color format is #RRGGBB[AA]");
3248                                 return;
3249                 }
3250                 if((ci=grc_conv(col_nam)) != -1){
3251                     im->graph_col[ci]=color;
3252                 }  else {
3253                   rrd_set_error("invalid color name '%s'",col_nam);
3254                   return;
3255                 }
3256             } else {
3257                 rrd_set_error("invalid color def format");
3258                 return;
3259             }
3260             break;        
3261         case 'n':{
3262             char prop[15];
3263             double size = 1;
3264             char font[1024] = "";
3265
3266             if(sscanf(optarg,
3267                                 "%10[A-Z]:%lf:%1000s",
3268                                 prop,&size,font) >= 2){
3269                 int sindex,propidx;
3270                 if((sindex=text_prop_conv(prop)) != -1){
3271                   for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){                      
3272                       if (size > 0){
3273                           im->text_prop[propidx].size=size;              
3274                       }
3275                       if (strlen(font) > 0){
3276                           strcpy(im->text_prop[propidx].font,font);
3277                       }
3278                       if (propidx==sindex && sindex != 0) break;
3279                   }
3280                 } else {
3281                     rrd_set_error("invalid fonttag '%s'",prop);
3282                     return;
3283                 }
3284             } else {
3285                 rrd_set_error("invalid text property format");
3286                 return;
3287             }
3288             break;          
3289         }
3290         case 'm':
3291             im->canvas->zoom = atof(optarg);
3292             if (im->canvas->zoom <= 0.0) {
3293                 rrd_set_error("zoom factor must be > 0");
3294                 return;
3295             }
3296           break;
3297         case 't':
3298             strncpy(im->title,optarg,150);
3299             im->title[150]='\0';
3300             break;
3301
3302         case 'R':
3303                 if ( strcmp( optarg, "normal" ) == 0 )
3304                         im->canvas->aa_type = AA_NORMAL;
3305                 else if ( strcmp( optarg, "light" ) == 0 )
3306                         im->canvas->aa_type = AA_LIGHT;
3307                 else if ( strcmp( optarg, "mono" ) == 0 )
3308                         im->canvas->aa_type = AA_NONE;
3309                 else
3310                 {
3311                         rrd_set_error("unknown font-render-mode '%s'", optarg );
3312                         return;
3313                 }
3314                 break;
3315
3316         case 'B':
3317             im->canvas->font_aa_threshold = atof(optarg);
3318                 break;
3319
3320         case 'W':
3321             strncpy(im->watermark,optarg,100);
3322             im->watermark[99]='\0';
3323             break;
3324
3325         case '?':
3326             if (optopt != 0)
3327                 rrd_set_error("unknown option '%c'", optopt);
3328             else
3329                 rrd_set_error("unknown option '%s'",argv[optind-1]);
3330             return;
3331         }
3332     }
3333     
3334     if (optind >= argc) {
3335        rrd_set_error("missing filename");
3336        return;
3337     }
3338
3339     if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
3340         rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");    
3341         return;
3342     }
3343
3344     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3345         /* error string is set in parsetime.c */
3346         return;
3347     }  
3348     
3349     if (start_tmp < 3600*24*365*10){
3350         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3351         return;
3352     }
3353     
3354     if (end_tmp < start_tmp) {
3355         rrd_set_error("start (%ld) should be less than end (%ld)", 
3356                start_tmp, end_tmp);
3357         return;
3358     }
3359     
3360     im->start = start_tmp;
3361     im->end = end_tmp;
3362     im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3363 }
3364
3365 int
3366 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3367 {
3368     if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3369         rrd_set_error("Unknown variable '%s' in %s",varname,err);
3370         return -1;
3371     }
3372     return 0;
3373 }
3374 int
3375 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3376 {
3377     char *color;
3378     graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3379
3380     color=strstr(var,"#");
3381     if (color==NULL) {
3382         if (optional==0) {
3383             rrd_set_error("Found no color in %s",err);
3384             return 0;
3385         }
3386         return 0;
3387     } else {
3388         int n=0;
3389         char *rest;
3390         gfx_color_t    col;
3391
3392         rest=strstr(color,":");
3393         if (rest!=NULL)
3394             n=rest-color;
3395         else
3396             n=strlen(color);
3397
3398         switch (n) {
3399             case 7:
3400                 sscanf(color,"#%6lx%n",&col,&n);
3401                 col = (col << 8) + 0xff /* shift left by 8 */;
3402                 if (n!=7) rrd_set_error("Color problem in %s",err);
3403                 break;
3404             case 9:
3405                 sscanf(color,"#%8lx%n",&col,&n);
3406                 if (n==9) break;
3407             default:
3408                 rrd_set_error("Color problem in %s",err);
3409         }
3410         if (rrd_test_error()) return 0;
3411         gdp->col = col;
3412         return n;
3413     }
3414 }
3415
3416
3417 int bad_format(char *fmt) {
3418     char *ptr;
3419     int n=0;
3420     ptr = fmt;
3421     while (*ptr != '\0')
3422         if (*ptr++ == '%') {
3423  
3424              /* line cannot end with percent char */
3425              if (*ptr == '\0') return 1;
3426  
3427              /* '%s', '%S' and '%%' are allowed */
3428              if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3429
3430              /* %c is allowed (but use only with vdef!) */
3431              else if (*ptr == 'c') {
3432                 ptr++;
3433                 n=1;
3434              }
3435
3436              /* or else '% 6.2lf' and such are allowed */
3437              else {
3438                  /* optional padding character */
3439                  if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3440
3441                  /* This should take care of 'm.n' with all three optional */
3442                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3443                  if (*ptr == '.') ptr++;
3444                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3445   
3446                  /* Either 'le', 'lf' or 'lg' must follow here */
3447                  if (*ptr++ != 'l') return 1;
3448                  if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3449                  else return 1;
3450                  n++;
3451             }
3452          }
3453       
3454       return (n!=1); 
3455 }
3456
3457
3458 int
3459 vdef_parse(gdes,str)
3460 struct graph_desc_t *gdes;
3461 const char *const str;
3462 {
3463     /* A VDEF currently is either "func" or "param,func"
3464      * so the parsing is rather simple.  Change if needed.
3465      */
3466     double      param;
3467     char        func[30];
3468     int         n;
3469     
3470     n=0;
3471     sscanf(str,"%le,%29[A-Z]%n",&param,func,&n);
3472     if (n== (int)strlen(str)) { /* matched */
3473         ;
3474     } else {
3475         n=0;
3476         sscanf(str,"%29[A-Z]%n",func,&n);
3477         if (n== (int)strlen(str)) { /* matched */
3478             param=DNAN;
3479         } else {
3480             rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3481                 ,str
3482                 ,gdes->vname
3483                 );
3484             return -1;
3485         }
3486     }
3487     if          (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3488     else if     (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3489     else if     (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3490     else if     (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3491     else if     (!strcmp("TOTAL",  func)) gdes->vf.op = VDEF_TOTAL;
3492     else if     (!strcmp("FIRST",  func)) gdes->vf.op = VDEF_FIRST;
3493     else if     (!strcmp("LAST",   func)) gdes->vf.op = VDEF_LAST;
3494     else if     (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3495     else if     (!strcmp("LSLINT",   func)) gdes->vf.op = VDEF_LSLINT;
3496     else if     (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3497     else {
3498         rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3499             ,func
3500             ,gdes->vname
3501             );
3502         return -1;
3503     };
3504
3505     switch (gdes->vf.op) {
3506         case VDEF_PERCENT:
3507             if (isnan(param)) { /* no parameter given */
3508                 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3509                     ,func
3510                     ,gdes->vname
3511                     );
3512                 return -1;
3513             };
3514             if (param>=0.0 && param<=100.0) {
3515                 gdes->vf.param = param;
3516                 gdes->vf.val   = DNAN;  /* undefined */
3517                 gdes->vf.when  = 0;     /* undefined */
3518             } else {
3519                 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3520                     ,param
3521                     ,gdes->vname
3522                     );
3523                 return -1;
3524             };
3525             break;
3526         case VDEF_MAXIMUM:
3527         case VDEF_AVERAGE:
3528         case VDEF_MINIMUM:
3529         case VDEF_TOTAL:
3530         case VDEF_FIRST:
3531         case VDEF_LAST:
3532         case VDEF_LSLSLOPE:
3533         case VDEF_LSLINT:
3534         case VDEF_LSLCORREL:
3535             if (isnan(param)) {
3536                 gdes->vf.param = DNAN;
3537                 gdes->vf.val   = DNAN;
3538                 gdes->vf.when  = 0;
3539             } else {
3540                 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3541                     ,func
3542                     ,gdes->vname
3543                     );
3544                 return -1;
3545             };
3546             break;
3547     };
3548     return 0;
3549 }
3550
3551
3552 int
3553 vdef_calc(im,gdi)
3554 image_desc_t *im;
3555 int gdi;
3556 {
3557     graph_desc_t        *src,*dst;
3558     rrd_value_t         *data;
3559     long                step,steps;
3560
3561     dst = &im->gdes[gdi];
3562     src = &im->gdes[dst->vidx];
3563     data = src->data + src->ds;
3564     steps = (src->end - src->start) / src->step;
3565
3566 #if 0
3567 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3568     ,src->start
3569     ,src->end
3570     ,steps
3571     );
3572 #endif
3573
3574     switch (dst->vf.op) {
3575         case VDEF_PERCENT: {
3576                 rrd_value_t *   array;
3577                 int             field;
3578
3579
3580                 if ((array = malloc(steps*sizeof(double)))==NULL) {
3581                     rrd_set_error("malloc VDEV_PERCENT");
3582                     return -1;
3583                 }
3584                 for (step=0;step < steps; step++) {
3585                     array[step]=data[step*src->ds_cnt];
3586                 }
3587                 qsort(array,step,sizeof(double),vdef_percent_compar);
3588
3589                 field = (steps-1)*dst->vf.param/100;
3590                 dst->vf.val  = array[field];
3591                 dst->vf.when = 0;       /* no time component */
3592                 free(array);
3593 #if 0
3594 for(step=0;step<steps;step++)
3595 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3596 #endif
3597             }
3598             break;
3599         case VDEF_MAXIMUM:
3600             step=0;
3601             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3602             if (step == steps) {
3603                 dst->vf.val  = DNAN;
3604                 dst->vf.when = 0;
3605             } else {
3606                 dst->vf.val  = data[step*src->ds_cnt];
3607                 dst->vf.when = src->start + (step+1)*src->step;
3608             }
3609             while (step != steps) {
3610                 if (finite(data[step*src->ds_cnt])) {
3611                     if (data[step*src->ds_cnt] > dst->vf.val) {
3612                         dst->vf.val  = data[step*src->ds_cnt];
3613                         dst->vf.when = src->start + (step+1)*src->step;
3614                     }
3615                 }
3616                 step++;
3617             }
3618             break;
3619         case VDEF_TOTAL:
3620         case VDEF_AVERAGE: {
3621             int cnt=0;
3622             double sum=0.0;
3623             for (step=0;step<steps;step++) {
3624                 if (finite(data[step*src->ds_cnt])) {
3625                     sum += data[step*src->ds_cnt];
3626                     cnt ++;
3627                 };
3628             }
3629             if (cnt) {
3630                 if (dst->vf.op == VDEF_TOTAL) {
3631                     dst->vf.val  = sum*src->step;
3632                     dst->vf.when = cnt*src->step;       /* not really "when" */
3633                 } else {
3634                     dst->vf.val = sum/cnt;
3635                     dst->vf.when = 0;   /* no time component */
3636                 };
3637             } else {
3638                 dst->vf.val  = DNAN;
3639                 dst->vf.when = 0;
3640             }
3641             }
3642             break;
3643         case VDEF_MINIMUM:
3644             step=0;
3645             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3646             if (step == steps) {
3647                 dst->vf.val  = DNAN;
3648                 dst->vf.when = 0;
3649             } else {
3650                 dst->vf.val  = data[step*src->ds_cnt];
3651                 dst->vf.when = src->start + (step+1)*src->step;
3652             }
3653             while (step != steps) {
3654                 if (finite(data[step*src->ds_cnt])) {
3655                     if (data[step*src->ds_cnt] < dst->vf.val) {
3656                         dst->vf.val  = data[step*src->ds_cnt];
3657                         dst->vf.when = src->start + (step+1)*src->step;
3658                     }
3659                 }
3660                 step++;
3661             }
3662             break;
3663         case VDEF_FIRST:
3664             /* The time value returned here is one step before the
3665              * actual time value.  This is the start of the first
3666              * non-NaN interval.
3667              */
3668             step=0;
3669             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3670             if (step == steps) { /* all entries were NaN */
3671                 dst->vf.val  = DNAN;
3672                 dst->vf.when = 0;
3673             } else {
3674                 dst->vf.val  = data[step*src->ds_cnt];
3675                 dst->vf.when = src->start + step*src->step;
3676             }
3677             break;
3678         case VDEF_LAST:
3679             /* The time value returned here is the
3680              * actual time value.  This is the end of the last
3681              * non-NaN interval.
3682              */
3683             step=steps-1;
3684             while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3685             if (step < 0) { /* all entries were NaN */
3686                 dst->vf.val  = DNAN;
3687                 dst->vf.when = 0;
3688             } else {
3689                 dst->vf.val  = data[step*src->ds_cnt];
3690                 dst->vf.when = src->start + (step+1)*src->step;
3691             }
3692             break;
3693         case VDEF_LSLSLOPE:
3694         case VDEF_LSLINT:
3695         case VDEF_LSLCORREL:{
3696             /* Bestfit line by linear least squares method */ 
3697
3698             int cnt=0;
3699             double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3700             SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3701
3702             for (step=0;step<steps;step++) {
3703                 if (finite(data[step*src->ds_cnt])) {
3704                     cnt++;
3705                     SUMx  += step;
3706                     SUMxx += step * step;
3707                     SUMxy += step * data[step*src->ds_cnt];
3708                     SUMy  += data[step*src->ds_cnt];
3709                     SUMyy  += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3710                 };
3711             }
3712
3713             slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3714             y_intercept = ( SUMy - slope*SUMx ) / cnt;
3715             correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3716
3717             if (cnt) {
3718                     if (dst->vf.op == VDEF_LSLSLOPE) {
3719                         dst->vf.val  = slope;
3720                         dst->vf.when = cnt*src->step;
3721                     } else if (dst->vf.op == VDEF_LSLINT)  {
3722                         dst->vf.val = y_intercept;
3723                         dst->vf.when = cnt*src->step;
3724                     } else if (dst->vf.op == VDEF_LSLCORREL)  {
3725                         dst->vf.val = correl;
3726                         dst->vf.when = cnt*src->step;
3727                     };
3728                 
3729             } else {
3730                 dst->vf.val  = DNAN;
3731                 dst->vf.when = 0;
3732             }
3733             }
3734             break;
3735     }
3736     return 0;
3737 }
3738
3739 /* NaN < -INF < finite_values < INF */
3740 int
3741 vdef_percent_compar(a,b)
3742 const void *a,*b;
3743 {
3744     /* Equality is not returned; this doesn't hurt except
3745      * (maybe) for a little performance.
3746      */
3747
3748     /* First catch NaN values. They are smallest */
3749     if (isnan( *(double *)a )) return -1;
3750     if (isnan( *(double *)b )) return  1;
3751
3752     /* NaN doesn't reach this part so INF and -INF are extremes.
3753      * The sign from isinf() is compatible with the sign we return
3754      */
3755     if (isinf( *(double *)a )) return isinf( *(double *)a );
3756     if (isinf( *(double *)b )) return isinf( *(double *)b );
3757
3758     /* If we reach this, both values must be finite */
3759     if ( *(double *)a < *(double *)b ) return -1; else return 1;
3760 }