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