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