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