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