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