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