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