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