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