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