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