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