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