contain a potential problem with log grid painting Bug #54
[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 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
1716 {
1717
1718     int aInt = *(int*)&A;
1719     int bInt = *(int*)&B;
1720     int intDiff;
1721     /* Make sure maxUlps is non-negative and small enough that the
1722        default NAN won't compare as equal to anything.  */
1723
1724     /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1725
1726     /* Make aInt lexicographically ordered as a twos-complement int */
1727
1728     if (aInt < 0)
1729         aInt = 0x80000000l - aInt;
1730
1731     /* Make bInt lexicographically ordered as a twos-complement int */
1732
1733     if (bInt < 0)
1734         bInt = 0x80000000l - bInt;
1735
1736     intDiff = abs(aInt - bInt);
1737
1738     if (intDiff <= maxUlps)
1739         return 1;
1740
1741     return 0;
1742 }
1743
1744 /* logaritmic horizontal grid */
1745 int
1746 horizontal_log_grid(image_desc_t   *im)   
1747 {
1748     double yloglab[][10] = {
1749         {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1750         {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1751         {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1752         {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1753         {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1754         {0,0,0,0,0, 0,0,0,0,0} /* last line */ };
1755
1756     int i, j, val_exp, min_exp;
1757     double nex;         /* number of decades in data */
1758     double logscale;    /* scale in logarithmic space */
1759     int exfrac = 1;     /* decade spacing */
1760     int mid = -1;       /* row in yloglab for major grid */
1761     double mspac;       /* smallest major grid spacing (pixels) */
1762     int flab;           /* first value in yloglab to use */
1763     double value, tmp, pre_value;
1764     double X0,X1,Y0;   
1765     char graph_label[100];
1766
1767     nex = log10(im->maxval / im->minval);
1768     logscale = im->ysize / nex;
1769
1770     /* major spacing for data with high dynamic range */
1771     while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1772         if(exfrac == 1) exfrac = 3;
1773         else exfrac += 3;
1774     }
1775
1776     /* major spacing for less dynamic data */
1777     do {
1778         /* search best row in yloglab */
1779         mid++;
1780         for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
1781         mspac = logscale * log10(10.0 / yloglab[mid][i]);
1782     } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
1783     if(mid) mid--;
1784
1785     /* find first value in yloglab */
1786     for(flab = 0; yloglab[mid][flab] < 10 && frexp10(im->minval, &tmp) > yloglab[mid][flab] ; flab++);
1787     if(yloglab[mid][flab] == 10.0) {
1788         tmp += 1.0;
1789         flab = 0;
1790     }
1791     val_exp = tmp;
1792     if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
1793
1794     X0=im->xorigin;
1795     X1=im->xorigin+im->xsize;
1796
1797     /* draw grid */
1798     pre_value = DNAN;
1799     while(1) {       
1800
1801         value = yloglab[mid][flab] * pow(10.0, val_exp);
1802         if (  AlmostEqual2sComplement(value,pre_value,4) ) break; /* it seems we are not converging */
1803
1804         pre_value = value;
1805
1806         Y0 = ytr(im, value);
1807         if(Y0 <= im->yorigin - im->ysize) break;
1808
1809         /* major grid line */
1810         gfx_new_dashed_line ( im->canvas,
1811             X0-2,Y0,
1812             X1+2,Y0,
1813             MGRIDWIDTH, im->graph_col[GRC_MGRID],
1814             im->grid_dash_on, im->grid_dash_off);
1815
1816         /* label */
1817         if (im->extra_flags & FORCE_UNITS_SI) {
1818             int scale;
1819             double pvalue;
1820             char symbol;
1821
1822             scale = floor(val_exp / 3.0);
1823             if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
1824             else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
1825             pvalue *= yloglab[mid][flab];
1826
1827             if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
1828                 ((scale+si_symbcenter) >= 0) )
1829                 symbol = si_symbol[scale+si_symbcenter];
1830             else
1831                 symbol = '?';
1832
1833                 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1834         } else
1835             sprintf(graph_label,"%3.0e", value);
1836         gfx_new_text ( im->canvas,
1837             X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1838             im->graph_col[GRC_FONT],
1839             im->text_prop[TEXT_PROP_AXIS].font,
1840             im->text_prop[TEXT_PROP_AXIS].size,
1841             im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1842             graph_label );
1843
1844         /* minor grid */
1845         if(mid < 4 && exfrac == 1) {
1846             /* find first and last minor line behind current major line
1847              * i is the first line and j tha last */
1848             if(flab == 0) {
1849                 min_exp = val_exp - 1;
1850                 for(i = 1; yloglab[mid][i] < 10.0; i++);
1851                 i = yloglab[mid][i - 1] + 1;
1852                 j = 10;
1853             }
1854             else {
1855                 min_exp = val_exp;
1856                 i = yloglab[mid][flab - 1] + 1;
1857                 j = yloglab[mid][flab];
1858             }
1859
1860             /* draw minor lines below current major line */
1861             for(; i < j; i++) {
1862
1863                 value = i * pow(10.0, min_exp);
1864                 if(value < im->minval) continue;
1865
1866                 Y0 = ytr(im, value);
1867                 if(Y0 <= im->yorigin - im->ysize) break;
1868
1869                 /* draw lines */
1870                 gfx_new_dashed_line ( im->canvas,
1871                     X0-1,Y0,
1872                     X1+1,Y0,
1873                     GRIDWIDTH, im->graph_col[GRC_GRID],
1874                     im->grid_dash_on, im->grid_dash_off);
1875             }
1876         }
1877         else if(exfrac > 1) {
1878             for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1879                 value = pow(10.0, i);
1880                 if(value < im->minval) continue;
1881
1882                 Y0 = ytr(im, value);
1883                 if(Y0 <= im->yorigin - im->ysize) break;
1884
1885                 /* draw lines */
1886                 gfx_new_dashed_line ( im->canvas,
1887                     X0-1,Y0,
1888                     X1+1,Y0,
1889                     GRIDWIDTH, im->graph_col[GRC_GRID],
1890                     im->grid_dash_on, im->grid_dash_off);
1891             }
1892         }
1893
1894         /* next decade */
1895         if(yloglab[mid][++flab] == 10.0) {
1896             flab = 0;
1897             val_exp += exfrac;
1898         }
1899     }
1900
1901     /* draw minor lines after highest major line */
1902     if(mid < 4 && exfrac == 1) {
1903         /* find first and last minor line below current major line
1904          * i is the first line and j tha last */
1905         if(flab == 0) {
1906             min_exp = val_exp - 1;
1907             for(i = 1; yloglab[mid][i] < 10.0; i++);
1908             i = yloglab[mid][i - 1] + 1;
1909             j = 10;
1910         }
1911         else {
1912             min_exp = val_exp;
1913             i = yloglab[mid][flab - 1] + 1;
1914             j = yloglab[mid][flab];
1915         }
1916
1917         /* draw minor lines below current major line */
1918         for(; i < j; i++) {
1919
1920             value = i * pow(10.0, min_exp);
1921             if(value < im->minval) continue;
1922
1923             Y0 = ytr(im, value);
1924             if(Y0 <= im->yorigin - im->ysize) break;
1925
1926             /* draw lines */
1927             gfx_new_dashed_line ( im->canvas,
1928                 X0-1,Y0,
1929                 X1+1,Y0,
1930                 GRIDWIDTH, im->graph_col[GRC_GRID],
1931                 im->grid_dash_on, im->grid_dash_off);
1932         }
1933     }
1934     /* fancy minor gridlines */
1935     else if(exfrac > 1) {
1936         for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1937             value = pow(10.0, i);
1938             if(value < im->minval) continue;
1939
1940             Y0 = ytr(im, value);
1941             if(Y0 <= im->yorigin - im->ysize) break;
1942
1943             /* draw lines */
1944             gfx_new_dashed_line ( im->canvas,
1945                 X0-1,Y0,
1946                 X1+1,Y0,
1947                 GRIDWIDTH, im->graph_col[GRC_GRID],
1948                 im->grid_dash_on, im->grid_dash_off);
1949         }
1950     }
1951
1952     return 1;
1953 }
1954
1955
1956 void
1957 vertical_grid(
1958     image_desc_t   *im )
1959 {   
1960     int xlab_sel;               /* which sort of label and grid ? */
1961     time_t ti, tilab, timajor;
1962     long factor;
1963     char graph_label[100];
1964     double X0,Y0,Y1; /* points for filled graph and more*/
1965     struct tm tm;
1966
1967     /* the type of time grid is determined by finding
1968        the number of seconds per pixel in the graph */
1969     
1970     
1971     if(im->xlab_user.minsec == -1){
1972         factor=(im->end - im->start)/im->xsize;
1973         xlab_sel=0;
1974         while ( xlab[xlab_sel+1].minsec != -1 
1975                 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; }   /* pick the last one */
1976         while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
1977                 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; }       /* go back to the smallest size */
1978         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1979         im->xlab_user.gridst = xlab[xlab_sel].gridst;
1980         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1981         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1982         im->xlab_user.labtm = xlab[xlab_sel].labtm;
1983         im->xlab_user.labst = xlab[xlab_sel].labst;
1984         im->xlab_user.precis = xlab[xlab_sel].precis;
1985         im->xlab_user.stst = xlab[xlab_sel].stst;
1986     }
1987     
1988     /* y coords are the same for every line ... */
1989     Y0 = im->yorigin;
1990     Y1 = im->yorigin-im->ysize;
1991    
1992
1993     /* paint the minor grid */
1994     if (!(im->extra_flags & NOMINOR))
1995     {
1996         for(ti = find_first_time(im->start,
1997                                 im->xlab_user.gridtm,
1998                                 im->xlab_user.gridst),
1999             timajor = find_first_time(im->start,
2000                                 im->xlab_user.mgridtm,
2001                                 im->xlab_user.mgridst);
2002             ti < im->end; 
2003             ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
2004             ){
2005             /* are we inside the graph ? */
2006             if (ti < im->start || ti > im->end) continue;
2007             while (timajor < ti) {
2008                 timajor = find_next_time(timajor,
2009                         im->xlab_user.mgridtm, im->xlab_user.mgridst);
2010             }
2011             if (ti == timajor) continue; /* skip as falls on major grid line */
2012            X0 = xtr(im,ti);       
2013            gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
2014                im->graph_col[GRC_GRID],
2015                im->grid_dash_on, im->grid_dash_off);
2016            
2017         }
2018     }
2019
2020     /* paint the major grid */
2021     for(ti = find_first_time(im->start,
2022                             im->xlab_user.mgridtm,
2023                             im->xlab_user.mgridst);
2024         ti < im->end; 
2025         ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
2026         ){
2027         /* are we inside the graph ? */
2028         if (ti < im->start || ti > im->end) continue;
2029        X0 = xtr(im,ti);
2030        gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
2031            im->graph_col[GRC_MGRID],
2032            im->grid_dash_on, im->grid_dash_off);
2033        
2034     }
2035     /* paint the labels below the graph */
2036     for(ti = find_first_time(im->start - im->xlab_user.precis/2,
2037                             im->xlab_user.labtm,
2038                             im->xlab_user.labst);
2039         ti <= im->end - im->xlab_user.precis/2; 
2040         ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
2041         ){
2042         tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
2043         /* are we inside the graph ? */
2044         if (tilab < im->start || tilab > im->end) continue;
2045
2046 #if HAVE_STRFTIME
2047         localtime_r(&tilab, &tm);
2048         strftime(graph_label,99,im->xlab_user.stst, &tm);
2049 #else
2050 # error "your libc has no strftime I guess we'll abort the exercise here."
2051 #endif
2052        gfx_new_text ( im->canvas,
2053                       xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
2054                       im->graph_col[GRC_FONT],
2055                       im->text_prop[TEXT_PROP_AXIS].font,
2056                       im->text_prop[TEXT_PROP_AXIS].size,
2057                       im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
2058                       graph_label );
2059        
2060     }
2061
2062 }
2063
2064
2065 void 
2066 axis_paint(
2067    image_desc_t   *im
2068            )
2069 {   
2070     /* draw x and y axis */
2071     /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2072                       im->xorigin+im->xsize,im->yorigin-im->ysize,
2073                       GRIDWIDTH, im->graph_col[GRC_AXIS]);
2074        
2075        gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2076                          im->xorigin+im->xsize,im->yorigin-im->ysize,
2077                          GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2078    
2079        gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
2080                          im->xorigin+im->xsize+4,im->yorigin,
2081                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2082    
2083        gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
2084                          im->xorigin,im->yorigin-im->ysize-4,
2085                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2086    
2087     
2088     /* arrow for X and Y axis direction */
2089     gfx_new_area ( im->canvas, 
2090                    im->xorigin+im->xsize+2,  im->yorigin-2,
2091                    im->xorigin+im->xsize+2,  im->yorigin+3,
2092                    im->xorigin+im->xsize+7,  im->yorigin+0.5, /* LINEOFFSET */
2093                    im->graph_col[GRC_ARROW]);
2094
2095     gfx_new_area ( im->canvas, 
2096                    im->xorigin-2,  im->yorigin-im->ysize-2,
2097                    im->xorigin+3,  im->yorigin-im->ysize-2,
2098                    im->xorigin+0.5,    im->yorigin-im->ysize-7, /* LINEOFFSET */
2099                    im->graph_col[GRC_ARROW]);
2100
2101 }
2102
2103 void
2104 grid_paint(image_desc_t   *im)
2105 {   
2106     long i;
2107     int res=0;
2108     double X0,Y0; /* points for filled graph and more*/
2109     gfx_node_t *node;
2110
2111     /* draw 3d border */
2112     node = gfx_new_area (im->canvas, 0,im->yimg,
2113                                  2,im->yimg-2,
2114                                  2,2,im->graph_col[GRC_SHADEA]);
2115     gfx_add_point( node , im->ximg - 2, 2 );
2116     gfx_add_point( node , im->ximg, 0 );
2117     gfx_add_point( node , 0,0 );
2118 /*    gfx_add_point( node , 0,im->yimg ); */
2119    
2120     node =  gfx_new_area (im->canvas, 2,im->yimg-2,
2121                                   im->ximg-2,im->yimg-2,
2122                                   im->ximg - 2, 2,
2123                                  im->graph_col[GRC_SHADEB]);
2124     gfx_add_point( node ,   im->ximg,0);
2125     gfx_add_point( node ,   im->ximg,im->yimg);
2126     gfx_add_point( node ,   0,im->yimg);
2127 /*    gfx_add_point( node , 0,im->yimg ); */
2128    
2129    
2130     if (im->draw_x_grid == 1 )
2131       vertical_grid(im);
2132     
2133     if (im->draw_y_grid == 1){
2134         if(im->logarithmic){
2135                 res = horizontal_log_grid(im);
2136         } else {
2137                 res = draw_horizontal_grid(im);
2138         }
2139         
2140         /* dont draw horizontal grid if there is no min and max val */
2141         if (! res ) {
2142           char *nodata = "No Data found";
2143            gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
2144                         im->graph_col[GRC_FONT],
2145                         im->text_prop[TEXT_PROP_AXIS].font,
2146                         im->text_prop[TEXT_PROP_AXIS].size,
2147                         im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
2148                         nodata );          
2149         }
2150     }
2151
2152     /* yaxis unit description */
2153     gfx_new_text( im->canvas,
2154                   10, (im->yorigin - im->ysize/2),
2155                   im->graph_col[GRC_FONT],
2156                   im->text_prop[TEXT_PROP_UNIT].font,
2157                   im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth, 
2158                   RRDGRAPH_YLEGEND_ANGLE,
2159                   GFX_H_LEFT, GFX_V_CENTER,
2160                   im->ylegend);
2161
2162     /* graph title */
2163     gfx_new_text( im->canvas,
2164                   im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2165                   im->graph_col[GRC_FONT],
2166                   im->text_prop[TEXT_PROP_TITLE].font,
2167                   im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2168                   GFX_H_CENTER, GFX_V_CENTER,
2169                   im->title);
2170     /* rrdtool 'logo' */
2171     gfx_new_text( im->canvas,
2172                   im->ximg-7, 7,
2173                   ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2174                   im->text_prop[TEXT_PROP_AXIS].font,
2175                   5.5, im->tabwidth, 270,
2176                   GFX_H_RIGHT, GFX_V_TOP,
2177                   "RRDTOOL / TOBI OETIKER");
2178
2179     /* graph watermark */
2180     if(im->watermark[0] != '\0') {
2181         gfx_new_text( im->canvas,
2182                   im->ximg/2, im->yimg-6,
2183                   ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2184                   im->text_prop[TEXT_PROP_AXIS].font,
2185                   5.5, im->tabwidth, 0,
2186                   GFX_H_CENTER, GFX_V_BOTTOM,
2187                   im->watermark);
2188     }
2189     
2190     /* graph labels */
2191     if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2192             for(i=0;i<im->gdes_c;i++){
2193                     if(im->gdes[i].legend[0] =='\0')
2194                             continue;
2195                     
2196                     /* im->gdes[i].leg_y is the bottom of the legend */
2197                     X0 = im->gdes[i].leg_x;
2198                     Y0 = im->gdes[i].leg_y;
2199                     gfx_new_text ( im->canvas, X0, Y0,
2200                                    im->graph_col[GRC_FONT],
2201                                    im->text_prop[TEXT_PROP_LEGEND].font,
2202                                    im->text_prop[TEXT_PROP_LEGEND].size,
2203                                    im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2204                                    im->gdes[i].legend );
2205                     /* The legend for GRAPH items starts with "M " to have
2206                        enough space for the box */
2207                     if (           im->gdes[i].gf != GF_PRINT &&
2208                                    im->gdes[i].gf != GF_GPRINT &&
2209                                    im->gdes[i].gf != GF_COMMENT) {
2210                             int boxH, boxV;
2211                             
2212                             boxH = gfx_get_text_width(im->canvas, 0,
2213                                                       im->text_prop[TEXT_PROP_LEGEND].font,
2214                                                       im->text_prop[TEXT_PROP_LEGEND].size,
2215                                                       im->tabwidth,"o", 0) * 1.2;
2216                             boxV = boxH*1.1;
2217                             
2218                             /* make sure transparent colors show up the same way as in the graph */
2219                              node = gfx_new_area(im->canvas,
2220                                                 X0,Y0-boxV,
2221                                                 X0,Y0,
2222                                                 X0+boxH,Y0,
2223                                                 im->graph_col[GRC_BACK]);
2224                             gfx_add_point ( node, X0+boxH, Y0-boxV );
2225
2226                             node = gfx_new_area(im->canvas,
2227                                                 X0,Y0-boxV,
2228                                                 X0,Y0,
2229                                                 X0+boxH,Y0,
2230                                                 im->gdes[i].col);
2231                             gfx_add_point ( node, X0+boxH, Y0-boxV );
2232                             node = gfx_new_line(im->canvas,
2233                                                 X0,Y0-boxV,
2234                                                 X0,Y0,
2235                                                 1.0,im->graph_col[GRC_FRAME]);
2236                             gfx_add_point(node,X0+boxH,Y0);
2237                             gfx_add_point(node,X0+boxH,Y0-boxV);
2238                             gfx_close_path(node);
2239                     }
2240             }
2241     }
2242 }
2243
2244
2245 /*****************************************************
2246  * lazy check make sure we rely need to create this graph
2247  *****************************************************/
2248
2249 int lazy_check(image_desc_t *im){
2250     FILE *fd = NULL;
2251         int size = 1;
2252     struct stat  imgstat;
2253     
2254     if (im->lazy == 0) return 0; /* no lazy option */
2255     if (stat(im->graphfile,&imgstat) != 0) 
2256       return 0; /* can't stat */
2257     /* one pixel in the existing graph is more then what we would
2258        change here ... */
2259     if (time(NULL) - imgstat.st_mtime > 
2260         (im->end - im->start) / im->xsize) 
2261       return 0;
2262     if ((fd = fopen(im->graphfile,"rb")) == NULL) 
2263       return 0; /* the file does not exist */
2264     switch (im->canvas->imgformat) {
2265     case IF_PNG:
2266            size = PngSize(fd,&(im->ximg),&(im->yimg));
2267            break;
2268     default:
2269            size = 1;
2270     }
2271     fclose(fd);
2272     return size;
2273 }
2274
2275 #ifdef WITH_PIECHART
2276 void
2277 pie_part(image_desc_t *im, gfx_color_t color,
2278             double PieCenterX, double PieCenterY, double Radius,
2279             double startangle, double endangle)
2280 {
2281     gfx_node_t *node;
2282     double angle;
2283     double step=M_PI/50; /* Number of iterations for the circle;
2284                          ** 10 is definitely too low, more than
2285                          ** 50 seems to be overkill
2286                          */
2287
2288     /* Strange but true: we have to work clockwise or else
2289     ** anti aliasing nor transparency don't work.
2290     **
2291     ** This test is here to make sure we do it right, also
2292     ** this makes the for...next loop more easy to implement.
2293     ** The return will occur if the user enters a negative number
2294     ** (which shouldn't be done according to the specs) or if the
2295     ** programmers do something wrong (which, as we all know, never
2296     ** happens anyway :)
2297     */
2298     if (endangle<startangle) return;
2299
2300     /* Hidden feature: Radius decreases each full circle */
2301     angle=startangle;
2302     while (angle>=2*M_PI) {
2303         angle  -= 2*M_PI;
2304         Radius *= 0.8;
2305     }
2306
2307     node=gfx_new_area(im->canvas,
2308                 PieCenterX+sin(startangle)*Radius,
2309                 PieCenterY-cos(startangle)*Radius,
2310                 PieCenterX,
2311                 PieCenterY,
2312                 PieCenterX+sin(endangle)*Radius,
2313                 PieCenterY-cos(endangle)*Radius,
2314                 color);
2315     for (angle=endangle;angle-startangle>=step;angle-=step) {
2316         gfx_add_point(node,
2317                 PieCenterX+sin(angle)*Radius,
2318                 PieCenterY-cos(angle)*Radius );
2319     }
2320 }
2321
2322 #endif
2323
2324 int
2325 graph_size_location(image_desc_t *im, int elements
2326
2327 #ifdef WITH_PIECHART
2328 , int piechart
2329 #endif
2330
2331  )
2332 {
2333     /* The actual size of the image to draw is determined from
2334     ** several sources.  The size given on the command line is
2335     ** the graph area but we need more as we have to draw labels
2336     ** and other things outside the graph area
2337     */
2338
2339     /* +-+-------------------------------------------+
2340     ** |l|.................title.....................|
2341     ** |e+--+-------------------------------+--------+
2342     ** |b| b|                               |        |
2343     ** |a| a|                               |  pie   |
2344     ** |l| l|          main graph area      | chart  |
2345     ** |.| .|                               |  area  |
2346     ** |t| y|                               |        |
2347     ** |r+--+-------------------------------+--------+
2348     ** |e|  | x-axis labels                 |        |
2349     ** |v+--+-------------------------------+--------+
2350     ** | |..............legends......................|
2351     ** +-+-------------------------------------------+
2352     ** |                 watermark                   |
2353     ** +---------------------------------------------+
2354     */
2355     int Xvertical=0,    
2356                         Ytitle   =0,
2357         Xylabel  =0,    
2358         Xmain    =0,    Ymain    =0,
2359 #ifdef WITH_PIECHART
2360         Xpie     =0,    Ypie     =0,
2361 #endif
2362                         Yxlabel  =0,
2363 #if 0
2364         Xlegend  =0,    Ylegend  =0,
2365 #endif
2366         Xspacing =15,  Yspacing =15,
2367        
2368                       Ywatermark =4;
2369
2370     if (im->extra_flags & ONLY_GRAPH) {
2371         im->xorigin =0;
2372         im->ximg = im->xsize;
2373         im->yimg = im->ysize;
2374         im->yorigin = im->ysize;
2375         ytr(im,DNAN); 
2376         return 0;
2377     }
2378
2379     if (im->ylegend[0] != '\0' ) {
2380            Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2381     }
2382
2383
2384     if (im->title[0] != '\0') {
2385         /* The title is placed "inbetween" two text lines so it
2386         ** automatically has some vertical spacing.  The horizontal
2387         ** spacing is added here, on each side.
2388         */
2389         /* don't care for the with of the title
2390                 Xtitle = gfx_get_text_width(im->canvas, 0,
2391                 im->text_prop[TEXT_PROP_TITLE].font,
2392                 im->text_prop[TEXT_PROP_TITLE].size,
2393                 im->tabwidth,
2394                 im->title, 0) + 2*Xspacing; */
2395         Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2396     }
2397
2398     if (elements) {
2399         Xmain=im->xsize;
2400         Ymain=im->ysize;
2401         if (im->draw_x_grid) {
2402             Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2403         }
2404         if (im->draw_y_grid) {
2405             Xylabel=gfx_get_text_width(im->canvas, 0,
2406                         im->text_prop[TEXT_PROP_AXIS].font,
2407                         im->text_prop[TEXT_PROP_AXIS].size,
2408                         im->tabwidth,
2409                         "0", 0) * im->unitslength;
2410         }
2411     }
2412
2413 #ifdef WITH_PIECHART
2414     if (piechart) {
2415         im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2416         Xpie=im->piesize;
2417         Ypie=im->piesize;
2418     }
2419 #endif
2420
2421     /* Now calculate the total size.  Insert some spacing where
2422        desired.  im->xorigin and im->yorigin need to correspond
2423        with the lower left corner of the main graph area or, if
2424        this one is not set, the imaginary box surrounding the
2425        pie chart area. */
2426
2427     /* The legend width cannot yet be determined, as a result we
2428     ** have problems adjusting the image to it.  For now, we just
2429     ** forget about it at all; the legend will have to fit in the
2430     ** size already allocated.
2431     */
2432     im->ximg = Xylabel + Xmain + 2 * Xspacing;
2433
2434 #ifdef WITH_PIECHART
2435     im->ximg  += Xpie;
2436 #endif
2437
2438     if (Xmain) im->ximg += Xspacing;
2439 #ifdef WITH_PIECHART
2440     if (Xpie) im->ximg += Xspacing;
2441 #endif
2442
2443     im->xorigin = Xspacing + Xylabel;
2444
2445     /* the length of the title should not influence with width of the graph
2446        if (Xtitle > im->ximg) im->ximg = Xtitle; */
2447
2448     if (Xvertical) { /* unit description */
2449         im->ximg += Xvertical;
2450         im->xorigin += Xvertical;
2451     }
2452     xtr(im,0);
2453
2454     /* The vertical size is interesting... we need to compare
2455     ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with 
2456     ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2457     ** in order to start even thinking about Ylegend or Ywatermark.
2458     **
2459     ** Do it in three portions: First calculate the inner part,
2460     ** then do the legend, then adjust the total height of the img,
2461     ** adding space for a watermark if one exists;
2462     */
2463
2464     /* reserve space for main and/or pie */
2465
2466     im->yimg = Ymain + Yxlabel;
2467     
2468 #ifdef WITH_PIECHART
2469     if (im->yimg < Ypie) im->yimg = Ypie;
2470 #endif
2471
2472     im->yorigin = im->yimg - Yxlabel;
2473
2474     /* reserve space for the title *or* some padding above the graph */
2475     if (Ytitle) {
2476         im->yimg += Ytitle;
2477         im->yorigin += Ytitle;
2478     } else {
2479         im->yimg += 1.5*Yspacing;
2480         im->yorigin += 1.5*Yspacing;
2481     }
2482     /* reserve space for padding below the graph */
2483     im->yimg += Yspacing;
2484      
2485     /* Determine where to place the legends onto the image.
2486     ** Adjust im->yimg to match the space requirements.
2487     */
2488     if(leg_place(im)==-1)
2489         return -1;
2490         
2491     if (im->watermark[0] != '\0') {
2492         im->yimg += Ywatermark;
2493     }
2494
2495 #if 0
2496     if (Xlegend > im->ximg) {
2497         im->ximg = Xlegend;
2498         /* reposition Pie */
2499     }
2500 #endif
2501
2502 #ifdef WITH_PIECHART
2503     /* The pie is placed in the upper right hand corner,
2504     ** just below the title (if any) and with sufficient
2505     ** padding.
2506     */
2507     if (elements) {
2508         im->pie_x = im->ximg - Xspacing - Xpie/2;
2509         im->pie_y = im->yorigin-Ymain+Ypie/2;
2510     } else {
2511         im->pie_x = im->ximg/2;
2512         im->pie_y = im->yorigin-Ypie/2;
2513     }
2514 #endif
2515
2516     ytr(im,DNAN);
2517     return 0;
2518 }
2519
2520 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2521 /* yes we are loosing precision by doing tos with floats instead of doubles
2522    but it seems more stable this way. */
2523    
2524
2525 /* draw that picture thing ... */
2526 int
2527 graph_paint(image_desc_t *im, char ***calcpr)
2528 {
2529   int i,ii;
2530   int lazy =     lazy_check(im);
2531 #ifdef WITH_PIECHART
2532   int piechart = 0;
2533   double PieStart=0.0;
2534 #endif
2535   FILE  *fo;
2536   gfx_node_t *node;
2537   
2538   double areazero = 0.0;
2539   graph_desc_t *lastgdes = NULL;    
2540
2541   /* if we are lazy and there is nothing to PRINT ... quit now */
2542   if (lazy && im->prt_c==0) return 0;
2543
2544   /* pull the data from the rrd files ... */
2545   
2546   if(data_fetch(im)==-1)
2547     return -1;
2548
2549   /* evaluate VDEF and CDEF operations ... */
2550   if(data_calc(im)==-1)
2551     return -1;
2552
2553 #ifdef WITH_PIECHART  
2554   /* check if we need to draw a piechart */
2555   for(i=0;i<im->gdes_c;i++){
2556     if (im->gdes[i].gf == GF_PART) {
2557       piechart=1;
2558       break;
2559     }
2560   }
2561 #endif
2562
2563   /* calculate and PRINT and GPRINT definitions. We have to do it at
2564    * this point because it will affect the length of the legends
2565    * if there are no graph elements we stop here ... 
2566    * if we are lazy, try to quit ... 
2567    */
2568   i=print_calc(im,calcpr);
2569   if(i<0) return -1;
2570   if(((i==0)
2571 #ifdef WITH_PIECHART
2572 &&(piechart==0)
2573 #endif
2574 ) || lazy) return 0;
2575
2576 #ifdef WITH_PIECHART
2577   /* If there's only the pie chart to draw, signal this */
2578   if (i==0) piechart=2;
2579 #endif
2580   
2581   /* get actual drawing data and find min and max values*/
2582   if(data_proc(im)==-1)
2583     return -1;
2584   
2585   if(!im->logarithmic){si_unit(im);}        /* identify si magnitude Kilo, Mega Giga ? */
2586   
2587   if(!im->rigid && ! im->logarithmic)
2588     expand_range(im);   /* make sure the upper and lower limit are
2589                            sensible values */
2590
2591   if (!calc_horizontal_grid(im))
2592     return -1;
2593
2594   if (im->gridfit)
2595     apply_gridfit(im);
2596
2597
2598 /**************************************************************
2599  *** Calculating sizes and locations became a bit confusing ***
2600  *** so I moved this into a separate function.              ***
2601  **************************************************************/
2602   if(graph_size_location(im,i
2603 #ifdef WITH_PIECHART
2604 ,piechart
2605 #endif
2606 )==-1)
2607     return -1;
2608
2609   /* the actual graph is created by going through the individual
2610      graph elements and then drawing them */
2611   
2612   node=gfx_new_area ( im->canvas,
2613                       0, 0,
2614                       0, im->yimg,
2615                       im->ximg, im->yimg,                      
2616                       im->graph_col[GRC_BACK]);
2617
2618   gfx_add_point(node,im->ximg, 0);
2619
2620 #ifdef WITH_PIECHART
2621   if (piechart != 2) {
2622 #endif
2623     node=gfx_new_area ( im->canvas,
2624                       im->xorigin,             im->yorigin, 
2625                       im->xorigin + im->xsize, im->yorigin,
2626                       im->xorigin + im->xsize, im->yorigin-im->ysize,
2627                       im->graph_col[GRC_CANVAS]);
2628   
2629     gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2630
2631     if (im->minval > 0.0)
2632       areazero = im->minval;
2633     if (im->maxval < 0.0)
2634       areazero = im->maxval;
2635 #ifdef WITH_PIECHART
2636    }
2637 #endif
2638
2639 #ifdef WITH_PIECHART
2640   if (piechart) {
2641     pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2642   }
2643 #endif
2644
2645   for(i=0;i<im->gdes_c;i++){
2646     switch(im->gdes[i].gf){
2647     case GF_CDEF:
2648     case GF_VDEF:
2649     case GF_DEF:
2650     case GF_PRINT:
2651     case GF_GPRINT:
2652     case GF_COMMENT:
2653     case GF_HRULE:
2654     case GF_VRULE:
2655     case GF_XPORT:
2656     case GF_SHIFT:
2657       break;
2658     case GF_TICK:
2659       for (ii = 0; ii < im->xsize; ii++)
2660         {
2661           if (!isnan(im->gdes[i].p_data[ii]) && 
2662               im->gdes[i].p_data[ii] != 0.0)
2663            { 
2664               if (im -> gdes[i].yrule > 0 ) {
2665                       gfx_new_line(im->canvas,
2666                                    im -> xorigin + ii, im->yorigin,
2667                                    im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2668                                    1.0,
2669                                    im -> gdes[i].col );
2670               } else if ( im -> gdes[i].yrule < 0 ) {
2671                       gfx_new_line(im->canvas,
2672                                    im -> xorigin + ii, im->yorigin - im -> ysize,
2673                                    im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2674                                    1.0,
2675                                    im -> gdes[i].col );
2676               
2677               }
2678            }
2679         }
2680       break;
2681     case GF_LINE:
2682     case GF_AREA:
2683       /* fix data points at oo and -oo */
2684       for(ii=0;ii<im->xsize;ii++){
2685         if (isinf(im->gdes[i].p_data[ii])){
2686           if (im->gdes[i].p_data[ii] > 0) {
2687             im->gdes[i].p_data[ii] = im->maxval ;
2688           } else {
2689             im->gdes[i].p_data[ii] = im->minval ;
2690           }                 
2691           
2692         }
2693       } /* for */
2694
2695       /* *******************************************************
2696        a           ___. (a,t) 
2697                   |   |    ___
2698               ____|   |   |   |
2699               |       |___|
2700        -------|--t-1--t--------------------------------      
2701                       
2702       if we know the value at time t was a then 
2703       we draw a square from t-1 to t with the value a.
2704
2705       ********************************************************* */
2706       if (im->gdes[i].col != 0x0){   
2707         /* GF_LINE and friend */
2708         if(im->gdes[i].gf == GF_LINE ){
2709           double last_y=0.0;
2710           node = NULL;
2711           for(ii=1;ii<im->xsize;ii++){
2712             if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2713                 node = NULL;
2714                 continue;
2715             }
2716             if ( node == NULL ) {
2717                 last_y = ytr(im,im->gdes[i].p_data[ii]);
2718                 if ( im->slopemode == 0 ){
2719                   node = gfx_new_line(im->canvas,
2720                                     ii-1+im->xorigin,last_y,
2721                                     ii+im->xorigin,last_y,
2722                                     im->gdes[i].linewidth,
2723                                     im->gdes[i].col);
2724                 } else {
2725                   node = gfx_new_line(im->canvas,
2726                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2727                                     ii+im->xorigin,last_y,
2728                                     im->gdes[i].linewidth,
2729                                     im->gdes[i].col);
2730                 }
2731              } else {
2732                double new_y = ytr(im,im->gdes[i].p_data[ii]);
2733                if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2734                    gfx_add_point(node,ii-1+im->xorigin,new_y);
2735                };
2736                last_y = new_y;
2737                gfx_add_point(node,ii+im->xorigin,new_y);
2738              };
2739
2740           }
2741         } else {
2742           int idxI=-1;
2743           double *foreY=malloc(sizeof(double)*im->xsize*2);
2744           double *foreX=malloc(sizeof(double)*im->xsize*2);
2745           double *backY=malloc(sizeof(double)*im->xsize*2);
2746           double *backX=malloc(sizeof(double)*im->xsize*2);
2747           int drawem = 0;
2748           for(ii=0;ii<=im->xsize;ii++){
2749             double ybase,ytop;
2750             if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2751                int cntI=1;
2752                int lastI=0;
2753                while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2754                node = gfx_new_area(im->canvas,
2755                                 backX[0],backY[0],
2756                                 foreX[0],foreY[0],
2757                                 foreX[cntI],foreY[cntI], im->gdes[i].col);
2758                while (cntI < idxI) {
2759                  lastI = cntI;
2760                  cntI++;
2761                  while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;} 
2762                  gfx_add_point(node,foreX[cntI],foreY[cntI]);
2763                }
2764                gfx_add_point(node,backX[idxI],backY[idxI]);
2765                while (idxI > 1){
2766                  lastI = idxI;
2767                  idxI--;
2768                  while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;} 
2769                  gfx_add_point(node,backX[idxI],backY[idxI]);
2770                }
2771                idxI=-1;
2772                drawem = 0;
2773             }
2774             if (drawem != 0){
2775               drawem = 0;
2776               idxI=-1;
2777             }
2778             if (ii == im->xsize) break;
2779             
2780             /* keep things simple for now, just draw these bars
2781                do not try to build a big and complex area */
2782
2783                                                               
2784             if ( im->slopemode == 0 && ii==0){
2785                 continue;
2786             }
2787             if ( isnan(im->gdes[i].p_data[ii]) ) {
2788                 drawem = 1;
2789                 continue;
2790             }
2791             ytop = ytr(im,im->gdes[i].p_data[ii]);
2792             if ( lastgdes && im->gdes[i].stack ) {
2793                   ybase = ytr(im,lastgdes->p_data[ii]);
2794             } else {
2795                   ybase = ytr(im,areazero);
2796             }
2797             if ( ybase == ytop ){
2798                 drawem = 1;
2799                 continue;       
2800             }
2801             /* every area has to be wound clock-wise,
2802                so we have to make sur base remains base  */             
2803             if (ybase > ytop){
2804                 double extra = ytop;
2805                 ytop = ybase;
2806                 ybase = extra;
2807             }
2808             if ( im->slopemode == 0 ){
2809                     backY[++idxI] = ybase-0.2;
2810                     backX[idxI] = ii+im->xorigin-1;
2811                     foreY[idxI] = ytop+0.2;
2812                     foreX[idxI] = ii+im->xorigin-1;
2813             }
2814             backY[++idxI] = ybase-0.2;
2815             backX[idxI] = ii+im->xorigin;
2816             foreY[idxI] = ytop+0.2;
2817             foreX[idxI] = ii+im->xorigin;
2818           }
2819           /* close up any remaining area */             
2820           free(foreY);
2821           free(foreX);
2822           free(backY);
2823           free(backX);
2824         } /* else GF_LINE */
2825       } /* if color != 0x0 */
2826       /* make sure we do not run into trouble when stacking on NaN */
2827       for(ii=0;ii<im->xsize;ii++){
2828         if (isnan(im->gdes[i].p_data[ii])) {
2829           if (lastgdes && (im->gdes[i].stack)) {
2830             im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2831           } else {
2832             im->gdes[i].p_data[ii] = areazero;
2833           }
2834         }
2835       } 
2836       lastgdes = &(im->gdes[i]);                         
2837       break;
2838 #ifdef WITH_PIECHART
2839     case GF_PART:
2840       if(isnan(im->gdes[i].yrule)) /* fetch variable */
2841         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2842      
2843       if (finite(im->gdes[i].yrule)) {  /* even the fetched var can be NaN */
2844         pie_part(im,im->gdes[i].col,
2845                 im->pie_x,im->pie_y,im->piesize*0.4,
2846                 M_PI*2.0*PieStart/100.0,
2847                 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2848         PieStart += im->gdes[i].yrule;
2849       }
2850       break;
2851 #endif
2852     case GF_STACK:
2853       rrd_set_error("STACK should already be turned into LINE or AREA here");
2854       return -1;
2855       break;
2856         
2857     } /* switch */
2858   }
2859 #ifdef WITH_PIECHART
2860   if (piechart==2) {
2861     im->draw_x_grid=0;
2862     im->draw_y_grid=0;
2863   }
2864 #endif
2865
2866
2867   /* grid_paint also does the text */
2868   if( !(im->extra_flags & ONLY_GRAPH) )  
2869     grid_paint(im);
2870
2871   
2872   if( !(im->extra_flags & ONLY_GRAPH) )  
2873       axis_paint(im);
2874   
2875   /* the RULES are the last thing to paint ... */
2876   for(i=0;i<im->gdes_c;i++){    
2877     
2878     switch(im->gdes[i].gf){
2879     case GF_HRULE:
2880       if(im->gdes[i].yrule >= im->minval
2881          && im->gdes[i].yrule <= im->maxval)
2882         gfx_new_line(im->canvas,
2883                      im->xorigin,ytr(im,im->gdes[i].yrule),
2884                      im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2885                      1.0,im->gdes[i].col); 
2886       break;
2887     case GF_VRULE:
2888       if(im->gdes[i].xrule >= im->start
2889          && im->gdes[i].xrule <= im->end)
2890         gfx_new_line(im->canvas,
2891                      xtr(im,im->gdes[i].xrule),im->yorigin,
2892                      xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2893                      1.0,im->gdes[i].col); 
2894       break;
2895     default:
2896       break;
2897     }
2898   }
2899
2900   
2901   if (strcmp(im->graphfile,"-")==0) {
2902     fo = im->graphhandle ? im->graphhandle : stdout;
2903 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2904     /* Change translation mode for stdout to BINARY */
2905     _setmode( _fileno( fo ), O_BINARY );
2906 #endif
2907   } else {
2908     if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2909       rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2910                     rrd_strerror(errno));
2911       return (-1);
2912     }
2913   }
2914   gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2915   if (strcmp(im->graphfile,"-") != 0)
2916     fclose(fo);
2917   return 0;
2918 }
2919
2920
2921 /*****************************************************
2922  * graph stuff 
2923  *****************************************************/
2924
2925 int
2926 gdes_alloc(image_desc_t *im){
2927
2928     im->gdes_c++;
2929     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2930                                            * sizeof(graph_desc_t)))==NULL){
2931         rrd_set_error("realloc graph_descs");
2932         return -1;
2933     }
2934
2935
2936     im->gdes[im->gdes_c-1].step=im->step;
2937     im->gdes[im->gdes_c-1].step_orig=im->step;
2938     im->gdes[im->gdes_c-1].stack=0;
2939     im->gdes[im->gdes_c-1].linewidth=0;
2940     im->gdes[im->gdes_c-1].debug=0;
2941     im->gdes[im->gdes_c-1].start=im->start; 
2942     im->gdes[im->gdes_c-1].start_orig=im->start; 
2943     im->gdes[im->gdes_c-1].end=im->end; 
2944     im->gdes[im->gdes_c-1].end_orig=im->end; 
2945     im->gdes[im->gdes_c-1].vname[0]='\0'; 
2946     im->gdes[im->gdes_c-1].data=NULL;
2947     im->gdes[im->gdes_c-1].ds_namv=NULL;
2948     im->gdes[im->gdes_c-1].data_first=0;
2949     im->gdes[im->gdes_c-1].p_data=NULL;
2950     im->gdes[im->gdes_c-1].rpnp=NULL;
2951     im->gdes[im->gdes_c-1].shift=0;
2952     im->gdes[im->gdes_c-1].col = 0x0;
2953     im->gdes[im->gdes_c-1].legend[0]='\0';
2954     im->gdes[im->gdes_c-1].format[0]='\0';
2955     im->gdes[im->gdes_c-1].strftm=0;   
2956     im->gdes[im->gdes_c-1].rrd[0]='\0';
2957     im->gdes[im->gdes_c-1].ds=-1;    
2958     im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;    
2959     im->gdes[im->gdes_c-1].cf=CF_AVERAGE;    
2960     im->gdes[im->gdes_c-1].p_data=NULL;    
2961     im->gdes[im->gdes_c-1].yrule=DNAN;
2962     im->gdes[im->gdes_c-1].xrule=0;
2963     return 0;
2964 }
2965
2966 /* copies input untill the first unescaped colon is found
2967    or until input ends. backslashes have to be escaped as well */
2968 int
2969 scan_for_col(const char *const input, int len, char *const output)
2970 {
2971     int inp,outp=0;
2972     for (inp=0; 
2973          inp < len &&
2974            input[inp] != ':' &&
2975            input[inp] != '\0';
2976          inp++){
2977       if (input[inp] == '\\' &&
2978           input[inp+1] != '\0' && 
2979           (input[inp+1] == '\\' ||
2980            input[inp+1] == ':')){
2981         output[outp++] = input[++inp];
2982       }
2983       else {
2984         output[outp++] = input[inp];
2985       }
2986     }
2987     output[outp] = '\0';
2988     return inp;
2989 }
2990 /* Some surgery done on this function, it became ridiculously big.
2991 ** Things moved:
2992 ** - initializing     now in rrd_graph_init()
2993 ** - options parsing  now in rrd_graph_options()
2994 ** - script parsing   now in rrd_graph_script()
2995 */
2996 int 
2997 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
2998 {
2999     image_desc_t   im;
3000     rrd_graph_init(&im);
3001     im.graphhandle = stream;
3002     
3003     rrd_graph_options(argc,argv,&im);
3004     if (rrd_test_error()) {
3005         im_free(&im);
3006         return -1;
3007     }
3008     
3009     if (strlen(argv[optind])>=MAXPATH) {
3010         rrd_set_error("filename (including path) too long");
3011         im_free(&im);
3012         return -1;
3013     }
3014     strncpy(im.graphfile,argv[optind],MAXPATH-1);
3015     im.graphfile[MAXPATH-1]='\0';
3016
3017     rrd_graph_script(argc,argv,&im,1);
3018     if (rrd_test_error()) {
3019         im_free(&im);
3020         return -1;
3021     }
3022
3023     /* Everything is now read and the actual work can start */
3024
3025     (*prdata)=NULL;
3026     if (graph_paint(&im,prdata)==-1){
3027         im_free(&im);
3028         return -1;
3029     }
3030
3031     /* The image is generated and needs to be output.
3032     ** Also, if needed, print a line with information about the image.
3033     */
3034
3035     *xsize=im.ximg;
3036     *ysize=im.yimg;
3037     *ymin=im.minval;
3038     *ymax=im.maxval;
3039     if (im.imginfo) {
3040         char *filename;
3041         if (!(*prdata)) {
3042             /* maybe prdata is not allocated yet ... lets do it now */
3043             if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
3044                 rrd_set_error("malloc imginfo");
3045                 return -1; 
3046             };
3047         }
3048         if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3049          ==NULL){
3050             rrd_set_error("malloc imginfo");
3051             return -1;
3052         }
3053         filename=im.graphfile+strlen(im.graphfile);
3054         while(filename > im.graphfile) {
3055             if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3056             filename--;
3057         }
3058
3059         sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
3060     }
3061     im_free(&im);
3062     return 0;
3063 }
3064
3065 void
3066 rrd_graph_init(image_desc_t *im)
3067 {
3068     unsigned int i;
3069
3070 #ifdef HAVE_TZSET
3071     tzset();
3072 #endif
3073 #ifdef HAVE_SETLOCALE
3074     setlocale(LC_TIME,"");
3075 #ifdef HAVE_MBSTOWCS
3076     setlocale(LC_CTYPE,"");
3077 #endif
3078 #endif
3079     im->yorigin=0;
3080     im->xorigin=0;
3081     im->minval=0;
3082     im->xlab_user.minsec = -1;
3083     im->ximg=0;
3084     im->yimg=0;
3085     im->xsize = 400;
3086     im->ysize = 100;
3087     im->step = 0;
3088     im->ylegend[0] = '\0';
3089     im->title[0] = '\0';
3090     im->watermark[0] = '\0';
3091     im->minval = DNAN;
3092     im->maxval = DNAN;    
3093     im->unitsexponent= 9999;
3094     im->unitslength= 6; 
3095     im->symbol = ' ';
3096     im->viewfactor = 1.0;
3097     im->extra_flags= 0;
3098     im->rigid = 0;
3099     im->gridfit = 1;
3100     im->imginfo = NULL;
3101     im->lazy = 0;
3102     im->slopemode = 0;
3103     im->logarithmic = 0;
3104     im->ygridstep = DNAN;
3105     im->draw_x_grid = 1;
3106     im->draw_y_grid = 1;
3107     im->base = 1000;
3108     im->prt_c = 0;
3109     im->gdes_c = 0;
3110     im->gdes = NULL;
3111     im->canvas = gfx_new_canvas();
3112     im->grid_dash_on = 1;
3113     im->grid_dash_off = 1;
3114     im->tabwidth = 40.0;
3115     
3116     for(i=0;i<DIM(graph_col);i++)
3117         im->graph_col[i]=graph_col[i];
3118
3119 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3120     {
3121             char *windir; 
3122             char rrd_win_default_font[1000];
3123             windir = getenv("windir");
3124             /* %windir% is something like D:\windows or C:\winnt */
3125             if (windir != NULL) {
3126                     strncpy(rrd_win_default_font,windir,500);
3127                     rrd_win_default_font[500] = '\0';
3128                     strcat(rrd_win_default_font,"\\fonts\\");
3129                     strcat(rrd_win_default_font,RRD_DEFAULT_FONT);         
3130                     for(i=0;i<DIM(text_prop);i++){
3131                             strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3132                             text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3133                      }
3134              }
3135     }
3136 #endif
3137     {
3138             char *deffont; 
3139             deffont = getenv("RRD_DEFAULT_FONT");
3140             if (deffont != NULL) {
3141                  for(i=0;i<DIM(text_prop);i++){
3142                         strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3143                         text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3144                  }
3145             }
3146     }
3147     for(i=0;i<DIM(text_prop);i++){        
3148       im->text_prop[i].size = text_prop[i].size;
3149       strcpy(im->text_prop[i].font,text_prop[i].font);
3150     }
3151 }
3152
3153 void
3154 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3155 {
3156     int                 stroff;    
3157     char                *parsetime_error = NULL;
3158     char                scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3159     time_t              start_tmp=0,end_tmp=0;
3160     long                long_tmp;
3161     struct rrd_time_value       start_tv, end_tv;
3162     gfx_color_t         color;
3163     optind = 0; opterr = 0;  /* initialize getopt */
3164
3165     parsetime("end-24h", &start_tv);
3166     parsetime("now", &end_tv);
3167
3168     /* defines for long options without a short equivalent. should be bytes,
3169        and may not collide with (the ASCII value of) short options */
3170     #define LONGOPT_UNITS_SI 255
3171
3172     while (1){
3173         static struct option long_options[] =
3174         {
3175             {"start",      required_argument, 0,  's'},
3176             {"end",        required_argument, 0,  'e'},
3177             {"x-grid",     required_argument, 0,  'x'},
3178             {"y-grid",     required_argument, 0,  'y'},
3179             {"vertical-label",required_argument,0,'v'},
3180             {"width",      required_argument, 0,  'w'},
3181             {"height",     required_argument, 0,  'h'},
3182             {"interlaced", no_argument,       0,  'i'},
3183             {"upper-limit",required_argument, 0,  'u'},
3184             {"lower-limit",required_argument, 0,  'l'},
3185             {"rigid",      no_argument,       0,  'r'},
3186             {"base",       required_argument, 0,  'b'},
3187             {"logarithmic",no_argument,       0,  'o'},
3188             {"color",      required_argument, 0,  'c'},
3189             {"font",       required_argument, 0,  'n'},
3190             {"title",      required_argument, 0,  't'},
3191             {"imginfo",    required_argument, 0,  'f'},
3192             {"imgformat",  required_argument, 0,  'a'},
3193             {"lazy",       no_argument,       0,  'z'},
3194             {"zoom",       required_argument, 0,  'm'},
3195             {"no-legend",  no_argument,       0,  'g'},
3196             {"force-rules-legend",no_argument,0,  'F'},
3197             {"only-graph", no_argument,       0,  'j'},
3198             {"alt-y-grid", no_argument,       0,  'Y'},
3199             {"no-minor",   no_argument,       0,  'I'},
3200             {"slope-mode", no_argument,       0,  'E'},
3201             {"alt-autoscale", no_argument,    0,  'A'},
3202             {"alt-autoscale-max", no_argument, 0, 'M'},
3203             {"no-gridfit", no_argument,       0,   'N'},
3204             {"units-exponent",required_argument, 0, 'X'},
3205             {"units-length",required_argument, 0, 'L'},
3206             {"units",      required_argument, 0,  LONGOPT_UNITS_SI },
3207             {"step",       required_argument, 0,    'S'},
3208             {"tabwidth",   required_argument, 0,    'T'},            
3209             {"font-render-mode", required_argument, 0, 'R'},
3210             {"font-smoothing-threshold", required_argument, 0, 'B'},
3211             {"watermark",  required_argument, 0,  'W'},
3212             {"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 */
3213             {0,0,0,0}};
3214         int option_index = 0;
3215         int opt;
3216         int col_start,col_end;
3217
3218         opt = getopt_long(argc, argv, 
3219                          "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3220                           long_options, &option_index);
3221
3222         if (opt == EOF)
3223             break;
3224         
3225         switch(opt) {
3226         case 'I':
3227             im->extra_flags |= NOMINOR;
3228             break;
3229         case 'Y':
3230             im->extra_flags |= ALTYGRID;
3231             break;
3232         case 'A':
3233             im->extra_flags |= ALTAUTOSCALE;
3234             break;
3235         case 'M':
3236             im->extra_flags |= ALTAUTOSCALE_MAX;
3237             break;
3238         case 'j':
3239            im->extra_flags |= ONLY_GRAPH;
3240            break;
3241         case 'g':
3242             im->extra_flags |= NOLEGEND;
3243             break;
3244         case 'F':
3245             im->extra_flags |= FORCE_RULES_LEGEND;
3246             break;
3247         case LONGOPT_UNITS_SI:
3248             if(im->extra_flags & FORCE_UNITS) {
3249                 rrd_set_error("--units can only be used once!");
3250                 return;
3251             }
3252             if(strcmp(optarg,"si")==0)
3253                 im->extra_flags |= FORCE_UNITS_SI;
3254             else {
3255                 rrd_set_error("invalid argument for --units: %s", optarg );
3256                 return;
3257             }
3258             break;
3259         case 'X':
3260             im->unitsexponent = atoi(optarg);
3261             break;
3262         case 'L':
3263             im->unitslength = atoi(optarg);
3264             break;
3265         case 'T':
3266             im->tabwidth = atof(optarg);
3267             break;
3268         case 'S':
3269             im->step =  atoi(optarg);
3270             break;
3271         case 'N':
3272             im->gridfit = 0;
3273             break;
3274         case 's':
3275             if ((parsetime_error = parsetime(optarg, &start_tv))) {
3276                 rrd_set_error( "start time: %s", parsetime_error );
3277                 return;
3278             }
3279             break;
3280         case 'e':
3281             if ((parsetime_error = parsetime(optarg, &end_tv))) {
3282                 rrd_set_error( "end time: %s", parsetime_error );
3283                 return;
3284             }
3285             break;
3286         case 'x':
3287             if(strcmp(optarg,"none") == 0){
3288               im->draw_x_grid=0;
3289               break;
3290             };
3291                 
3292             if(sscanf(optarg,
3293                       "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3294                       scan_gtm,
3295                       &im->xlab_user.gridst,
3296                       scan_mtm,
3297                       &im->xlab_user.mgridst,
3298                       scan_ltm,
3299                       &im->xlab_user.labst,
3300                       &im->xlab_user.precis,
3301                       &stroff) == 7 && stroff != 0){
3302                 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3303                 im->xlab_form[sizeof(im->xlab_form)-1] = '\0'; 
3304                 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3305                     rrd_set_error("unknown keyword %s",scan_gtm);
3306                     return;
3307                 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3308                     rrd_set_error("unknown keyword %s",scan_mtm);
3309                     return;
3310                 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3311                     rrd_set_error("unknown keyword %s",scan_ltm);
3312                     return;
3313                 } 
3314                 im->xlab_user.minsec = 1;
3315                 im->xlab_user.stst = im->xlab_form;
3316             } else {
3317                 rrd_set_error("invalid x-grid format");
3318                 return;
3319             }
3320             break;
3321         case 'y':
3322
3323             if(strcmp(optarg,"none") == 0){
3324               im->draw_y_grid=0;
3325               break;
3326             };
3327
3328             if(sscanf(optarg,
3329                       "%lf:%d",
3330                       &im->ygridstep,
3331                       &im->ylabfact) == 2) {
3332                 if(im->ygridstep<=0){
3333                     rrd_set_error("grid step must be > 0");
3334                     return;
3335                 } else if (im->ylabfact < 1){
3336                     rrd_set_error("label factor must be > 0");
3337                     return;
3338                 } 
3339             } else {
3340                 rrd_set_error("invalid y-grid format");
3341                 return;
3342             }
3343             break;
3344         case 'v':
3345             strncpy(im->ylegend,optarg,150);
3346             im->ylegend[150]='\0';
3347             break;
3348         case 'u':
3349             im->maxval = atof(optarg);
3350             break;
3351         case 'l':
3352             im->minval = atof(optarg);
3353             break;
3354         case 'b':
3355             im->base = atol(optarg);
3356             if(im->base != 1024 && im->base != 1000 ){
3357                 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3358                 return;
3359             }
3360             break;
3361         case 'w':
3362             long_tmp = atol(optarg);
3363             if (long_tmp < 10) {
3364                 rrd_set_error("width below 10 pixels");
3365                 return;
3366             }
3367             im->xsize = long_tmp;
3368             break;
3369         case 'h':
3370             long_tmp = atol(optarg);
3371             if (long_tmp < 10) {
3372                 rrd_set_error("height below 10 pixels");
3373                 return;
3374             }
3375             im->ysize = long_tmp;
3376             break;
3377         case 'i':
3378             im->canvas->interlaced = 1;
3379             break;
3380         case 'r':
3381             im->rigid = 1;
3382             break;
3383         case 'f':
3384             im->imginfo = optarg;
3385             break;
3386         case 'a':
3387             if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3388                 rrd_set_error("unsupported graphics format '%s'",optarg);
3389                 return;
3390             }
3391             break;
3392         case 'z':
3393             im->lazy = 1;
3394             break;
3395         case 'E':
3396             im->slopemode = 1;
3397             break;
3398
3399         case 'o':
3400             im->logarithmic = 1;
3401             break;
3402         case 'c':
3403             if(sscanf(optarg,
3404                       "%10[A-Z]#%n%8lx%n",
3405                       col_nam,&col_start,&color,&col_end) == 2){
3406                 int ci;
3407                 int col_len = col_end - col_start;
3408                 switch (col_len){
3409                         case 3:
3410                                 color = (
3411                                         ((color & 0xF00) * 0x110000) |
3412                                         ((color & 0x0F0) * 0x011000) |
3413                                         ((color & 0x00F) * 0x001100) |
3414                                         0x000000FF
3415                                         );
3416                                 break;
3417                         case 4:
3418                                 color = (
3419                                         ((color & 0xF000) * 0x11000) |
3420                                         ((color & 0x0F00) * 0x01100) |
3421                                         ((color & 0x00F0) * 0x00110) |
3422                                         ((color & 0x000F) * 0x00011)
3423                                         );
3424                                 break;
3425                         case 6:
3426                                 color = (color << 8) + 0xff /* shift left by 8 */;
3427                                 break;
3428                         case 8:
3429                                 break;
3430                         default:
3431                                 rrd_set_error("the color format is #RRGGBB[AA]");
3432                                 return;
3433                 }
3434                 if((ci=grc_conv(col_nam)) != -1){
3435                     im->graph_col[ci]=color;
3436                 }  else {
3437                   rrd_set_error("invalid color name '%s'",col_nam);
3438                   return;
3439                 }
3440             } else {
3441                 rrd_set_error("invalid color def format");
3442                 return;
3443             }
3444             break;        
3445         case 'n':{
3446             char prop[15];
3447             double size = 1;
3448             char font[1024] = "";
3449
3450             if(sscanf(optarg,
3451                                 "%10[A-Z]:%lf:%1000s",
3452                                 prop,&size,font) >= 2){
3453                 int sindex,propidx;
3454                 if((sindex=text_prop_conv(prop)) != -1){
3455                   for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){                      
3456                       if (size > 0){
3457                           im->text_prop[propidx].size=size;              
3458                       }
3459                       if (strlen(font) > 0){
3460                           strcpy(im->text_prop[propidx].font,font);
3461                       }
3462                       if (propidx==sindex && sindex != 0) break;
3463                   }
3464                 } else {
3465                     rrd_set_error("invalid fonttag '%s'",prop);
3466                     return;
3467                 }
3468             } else {
3469                 rrd_set_error("invalid text property format");
3470                 return;
3471             }
3472             break;          
3473         }
3474         case 'm':
3475             im->canvas->zoom = atof(optarg);
3476             if (im->canvas->zoom <= 0.0) {
3477                 rrd_set_error("zoom factor must be > 0");
3478                 return;
3479             }
3480           break;
3481         case 't':
3482             strncpy(im->title,optarg,150);
3483             im->title[150]='\0';
3484             break;
3485
3486         case 'R':
3487                 if ( strcmp( optarg, "normal" ) == 0 )
3488                         im->canvas->aa_type = AA_NORMAL;
3489                 else if ( strcmp( optarg, "light" ) == 0 )
3490                         im->canvas->aa_type = AA_LIGHT;
3491                 else if ( strcmp( optarg, "mono" ) == 0 )
3492                         im->canvas->aa_type = AA_NONE;
3493                 else
3494                 {
3495                         rrd_set_error("unknown font-render-mode '%s'", optarg );
3496                         return;
3497                 }
3498                 break;
3499
3500         case 'B':
3501             im->canvas->font_aa_threshold = atof(optarg);
3502                 break;
3503
3504         case 'W':
3505             strncpy(im->watermark,optarg,100);
3506             im->watermark[99]='\0';
3507             break;
3508
3509         case '?':
3510             if (optopt != 0)
3511                 rrd_set_error("unknown option '%c'", optopt);
3512             else
3513                 rrd_set_error("unknown option '%s'",argv[optind-1]);
3514             return;
3515         }
3516     }
3517     
3518     if (optind >= argc) {
3519        rrd_set_error("missing filename");
3520        return;
3521     }
3522
3523     if (im->logarithmic == 1 && im->minval <= 0){
3524         rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");    
3525         return;
3526     }
3527
3528     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3529         /* error string is set in parsetime.c */
3530         return;
3531     }  
3532     
3533     if (start_tmp < 3600*24*365*10){
3534         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3535         return;
3536     }
3537     
3538     if (end_tmp < start_tmp) {
3539         rrd_set_error("start (%ld) should be less than end (%ld)", 
3540                start_tmp, end_tmp);
3541         return;
3542     }
3543     
3544     im->start = start_tmp;
3545     im->end = end_tmp;
3546     im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3547 }
3548
3549 int
3550 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3551 {
3552     if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3553         rrd_set_error("Unknown variable '%s' in %s",varname,err);
3554         return -1;
3555     }
3556     return 0;
3557 }
3558 int
3559 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3560 {
3561     char *color;
3562     graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3563
3564     color=strstr(var,"#");
3565     if (color==NULL) {
3566         if (optional==0) {
3567             rrd_set_error("Found no color in %s",err);
3568             return 0;
3569         }
3570         return 0;
3571     } else {
3572         int n=0;
3573         char *rest;
3574         gfx_color_t    col;
3575
3576         rest=strstr(color,":");
3577         if (rest!=NULL)
3578             n=rest-color;
3579         else
3580             n=strlen(color);
3581
3582         switch (n) {
3583             case 7:
3584                 sscanf(color,"#%6lx%n",&col,&n);
3585                 col = (col << 8) + 0xff /* shift left by 8 */;
3586                 if (n!=7) rrd_set_error("Color problem in %s",err);
3587                 break;
3588             case 9:
3589                 sscanf(color,"#%8lx%n",&col,&n);
3590                 if (n==9) break;
3591             default:
3592                 rrd_set_error("Color problem in %s",err);
3593         }
3594         if (rrd_test_error()) return 0;
3595         gdp->col = col;
3596         return n;
3597     }
3598 }
3599
3600
3601 int bad_format(char *fmt) {
3602     char *ptr;
3603     int n=0;
3604     ptr = fmt;
3605     while (*ptr != '\0')
3606         if (*ptr++ == '%') {
3607  
3608              /* line cannot end with percent char */
3609              if (*ptr == '\0') return 1;
3610  
3611              /* '%s', '%S' and '%%' are allowed */
3612              if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3613
3614              /* %c is allowed (but use only with vdef!) */
3615              else if (*ptr == 'c') {
3616                 ptr++;
3617                 n=1;
3618              }
3619
3620              /* or else '% 6.2lf' and such are allowed */
3621              else {
3622                  /* optional padding character */
3623                  if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3624
3625                  /* This should take care of 'm.n' with all three optional */
3626                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3627                  if (*ptr == '.') ptr++;
3628                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3629   
3630                  /* Either 'le', 'lf' or 'lg' must follow here */
3631                  if (*ptr++ != 'l') return 1;
3632                  if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3633                  else return 1;
3634                  n++;
3635             }
3636          }
3637       
3638       return (n!=1); 
3639 }
3640
3641
3642 int
3643 vdef_parse(gdes,str)
3644 struct graph_desc_t *gdes;
3645 const char *const str;
3646 {
3647     /* A VDEF currently is either "func" or "param,func"
3648      * so the parsing is rather simple.  Change if needed.
3649      */
3650     double      param;
3651     char        func[30];
3652     int         n;
3653     
3654     n=0;
3655     sscanf(str,"%le,%29[A-Z]%n",&param,func,&n);
3656     if (n== (int)strlen(str)) { /* matched */
3657         ;
3658     } else {
3659         n=0;
3660         sscanf(str,"%29[A-Z]%n",func,&n);
3661         if (n== (int)strlen(str)) { /* matched */
3662             param=DNAN;
3663         } else {
3664             rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3665                 ,str
3666                 ,gdes->vname
3667                 );
3668             return -1;
3669         }
3670     }
3671     if          (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3672     else if     (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3673     else if     (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3674     else if     (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3675     else if     (!strcmp("TOTAL",  func)) gdes->vf.op = VDEF_TOTAL;
3676     else if     (!strcmp("FIRST",  func)) gdes->vf.op = VDEF_FIRST;
3677     else if     (!strcmp("LAST",   func)) gdes->vf.op = VDEF_LAST;
3678     else if     (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3679     else if     (!strcmp("LSLINT",   func)) gdes->vf.op = VDEF_LSLINT;
3680     else if     (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3681     else {
3682         rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3683             ,func
3684             ,gdes->vname
3685             );
3686         return -1;
3687     };
3688
3689     switch (gdes->vf.op) {
3690         case VDEF_PERCENT:
3691             if (isnan(param)) { /* no parameter given */
3692                 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3693                     ,func
3694                     ,gdes->vname
3695                     );
3696                 return -1;
3697             };
3698             if (param>=0.0 && param<=100.0) {
3699                 gdes->vf.param = param;
3700                 gdes->vf.val   = DNAN;  /* undefined */
3701                 gdes->vf.when  = 0;     /* undefined */
3702             } else {
3703                 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3704                     ,param
3705                     ,gdes->vname
3706                     );
3707                 return -1;
3708             };
3709             break;
3710         case VDEF_MAXIMUM:
3711         case VDEF_AVERAGE:
3712         case VDEF_MINIMUM:
3713         case VDEF_TOTAL:
3714         case VDEF_FIRST:
3715         case VDEF_LAST:
3716         case VDEF_LSLSLOPE:
3717         case VDEF_LSLINT:
3718         case VDEF_LSLCORREL:
3719             if (isnan(param)) {
3720                 gdes->vf.param = DNAN;
3721                 gdes->vf.val   = DNAN;
3722                 gdes->vf.when  = 0;
3723             } else {
3724                 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3725                     ,func
3726                     ,gdes->vname
3727                     );
3728                 return -1;
3729             };
3730             break;
3731     };
3732     return 0;
3733 }
3734
3735
3736 int
3737 vdef_calc(im,gdi)
3738 image_desc_t *im;
3739 int gdi;
3740 {
3741     graph_desc_t        *src,*dst;
3742     rrd_value_t         *data;
3743     long                step,steps;
3744
3745     dst = &im->gdes[gdi];
3746     src = &im->gdes[dst->vidx];
3747     data = src->data + src->ds;
3748     steps = (src->end - src->start) / src->step;
3749
3750 #if 0
3751 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3752     ,src->start
3753     ,src->end
3754     ,steps
3755     );
3756 #endif
3757
3758     switch (dst->vf.op) {
3759         case VDEF_PERCENT: {
3760                 rrd_value_t *   array;
3761                 int             field;
3762
3763
3764                 if ((array = malloc(steps*sizeof(double)))==NULL) {
3765                     rrd_set_error("malloc VDEV_PERCENT");
3766                     return -1;
3767                 }
3768                 for (step=0;step < steps; step++) {
3769                     array[step]=data[step*src->ds_cnt];
3770                 }
3771                 qsort(array,step,sizeof(double),vdef_percent_compar);
3772
3773                 field = (steps-1)*dst->vf.param/100;
3774                 dst->vf.val  = array[field];
3775                 dst->vf.when = 0;       /* no time component */
3776                 free(array);
3777 #if 0
3778 for(step=0;step<steps;step++)
3779 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3780 #endif
3781             }
3782             break;
3783         case VDEF_MAXIMUM:
3784             step=0;
3785             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3786             if (step == steps) {
3787                 dst->vf.val  = DNAN;
3788                 dst->vf.when = 0;
3789             } else {
3790                 dst->vf.val  = data[step*src->ds_cnt];
3791                 dst->vf.when = src->start + (step+1)*src->step;
3792             }
3793             while (step != steps) {
3794                 if (finite(data[step*src->ds_cnt])) {
3795                     if (data[step*src->ds_cnt] > dst->vf.val) {
3796                         dst->vf.val  = data[step*src->ds_cnt];
3797                         dst->vf.when = src->start + (step+1)*src->step;
3798                     }
3799                 }
3800                 step++;
3801             }
3802             break;
3803         case VDEF_TOTAL:
3804         case VDEF_AVERAGE: {
3805             int cnt=0;
3806             double sum=0.0;
3807             for (step=0;step<steps;step++) {
3808                 if (finite(data[step*src->ds_cnt])) {
3809                     sum += data[step*src->ds_cnt];
3810                     cnt ++;
3811                 };
3812             }
3813             if (cnt) {
3814                 if (dst->vf.op == VDEF_TOTAL) {
3815                     dst->vf.val  = sum*src->step;
3816                     dst->vf.when = 0;   /* no time component */
3817                 } else {
3818                     dst->vf.val = sum/cnt;
3819                     dst->vf.when = 0;   /* no time component */
3820                 };
3821             } else {
3822                 dst->vf.val  = DNAN;
3823                 dst->vf.when = 0;
3824             }
3825             }
3826             break;
3827         case VDEF_MINIMUM:
3828             step=0;
3829             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3830             if (step == steps) {
3831                 dst->vf.val  = DNAN;
3832                 dst->vf.when = 0;
3833             } else {
3834                 dst->vf.val  = data[step*src->ds_cnt];
3835                 dst->vf.when = src->start + (step+1)*src->step;
3836             }
3837             while (step != steps) {
3838                 if (finite(data[step*src->ds_cnt])) {
3839                     if (data[step*src->ds_cnt] < dst->vf.val) {
3840                         dst->vf.val  = data[step*src->ds_cnt];
3841                         dst->vf.when = src->start + (step+1)*src->step;
3842                     }
3843                 }
3844                 step++;
3845             }
3846             break;
3847         case VDEF_FIRST:
3848             /* The time value returned here is one step before the
3849              * actual time value.  This is the start of the first
3850              * non-NaN interval.
3851              */
3852             step=0;
3853             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3854             if (step == steps) { /* all entries were NaN */
3855                 dst->vf.val  = DNAN;
3856                 dst->vf.when = 0;
3857             } else {
3858                 dst->vf.val  = data[step*src->ds_cnt];
3859                 dst->vf.when = src->start + step*src->step;
3860             }
3861             break;
3862         case VDEF_LAST:
3863             /* The time value returned here is the
3864              * actual time value.  This is the end of the last
3865              * non-NaN interval.
3866              */
3867             step=steps-1;
3868             while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3869             if (step < 0) { /* all entries were NaN */
3870                 dst->vf.val  = DNAN;
3871                 dst->vf.when = 0;
3872             } else {
3873                 dst->vf.val  = data[step*src->ds_cnt];
3874                 dst->vf.when = src->start + (step+1)*src->step;
3875             }
3876             break;
3877         case VDEF_LSLSLOPE:
3878         case VDEF_LSLINT:
3879         case VDEF_LSLCORREL:{
3880             /* Bestfit line by linear least squares method */ 
3881
3882             int cnt=0;
3883             double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3884             SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3885
3886             for (step=0;step<steps;step++) {
3887                 if (finite(data[step*src->ds_cnt])) {
3888                     cnt++;
3889                     SUMx  += step;
3890                     SUMxx += step * step;
3891                     SUMxy += step * data[step*src->ds_cnt];
3892                     SUMy  += data[step*src->ds_cnt];
3893                     SUMyy  += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3894                 };
3895             }
3896
3897             slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3898             y_intercept = ( SUMy - slope*SUMx ) / cnt;
3899             correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3900
3901             if (cnt) {
3902                     if (dst->vf.op == VDEF_LSLSLOPE) {
3903                         dst->vf.val  = slope;
3904                         dst->vf.when = 0;
3905                     } else if (dst->vf.op == VDEF_LSLINT)  {
3906                         dst->vf.val = y_intercept;
3907                         dst->vf.when = 0;
3908                     } else if (dst->vf.op == VDEF_LSLCORREL)  {
3909                         dst->vf.val = correl;
3910                         dst->vf.when = 0;
3911                     };
3912                 
3913             } else {
3914                 dst->vf.val  = DNAN;
3915                 dst->vf.when = 0;
3916             }
3917         }
3918         break;
3919     }
3920     return 0;
3921 }
3922
3923 /* NaN < -INF < finite_values < INF */
3924 int
3925 vdef_percent_compar(a,b)
3926 const void *a,*b;
3927 {
3928     /* Equality is not returned; this doesn't hurt except
3929      * (maybe) for a little performance.
3930      */
3931
3932     /* First catch NaN values. They are smallest */
3933     if (isnan( *(double *)a )) return -1;
3934     if (isnan( *(double *)b )) return  1;
3935
3936     /* NaN doesn't reach this part so INF and -INF are extremes.
3937      * The sign from isinf() is compatible with the sign we return
3938      */
3939     if (isinf( *(double *)a )) return isinf( *(double *)a );
3940     if (isinf( *(double *)b )) return isinf( *(double *)b );
3941
3942     /* If we reach this, both values must be finite */
3943     if ( *(double *)a < *(double *)b ) return -1; else return 1;
3944 }