prepare for the release of rrdtool-1.2.12
[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;
1531     int      decimals, fractionals;
1532
1533     im->ygrid_scale.labfact=2;
1534     gridind=-1;
1535     range =  im->maxval - im->minval;
1536     scaledrange = range / im->magfact;
1537
1538         /* does the scale of this graph make it impossible to put lines
1539            on it? If so, give up. */
1540         if (isnan(scaledrange)) {
1541                 return 0;
1542         }
1543
1544     /* find grid spaceing */
1545     pixel=1;
1546     if(isnan(im->ygridstep)){
1547         if(im->extra_flags & ALTYGRID) {
1548             /* find the value with max number of digits. Get number of digits */
1549             decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1550             if(decimals <= 0) /* everything is small. make place for zero */
1551                 decimals = 1;
1552             
1553             im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1554             
1555             if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1556                 im->ygrid_scale.gridstep = 0.1;
1557             /* should have at least 5 lines but no more then 15 */
1558             if(range/im->ygrid_scale.gridstep < 5)
1559                 im->ygrid_scale.gridstep /= 10;
1560             if(range/im->ygrid_scale.gridstep > 15)
1561                 im->ygrid_scale.gridstep *= 10;
1562             if(range/im->ygrid_scale.gridstep > 5) {
1563                 im->ygrid_scale.labfact = 1;
1564                 if(range/im->ygrid_scale.gridstep > 8)
1565                     im->ygrid_scale.labfact = 2;
1566             }
1567             else {
1568                 im->ygrid_scale.gridstep /= 5;
1569                 im->ygrid_scale.labfact = 5;
1570             }
1571             fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1572             if(fractionals < 0) { /* small amplitude. */
1573                 int len = decimals - fractionals + 1;
1574                 if (im->unitslength < len+2) im->unitslength = len+2;
1575                 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1576             } else {
1577                 int len = decimals + 1;
1578                 if (im->unitslength < len+2) im->unitslength = len+2;
1579                 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1580             }
1581         }
1582         else {
1583             for(i=0;ylab[i].grid > 0;i++){
1584                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1585                 if (pixel > 7) {
1586                     gridind = i;
1587                     break;
1588                 }
1589             }
1590             
1591             for(i=0; i<4;i++) {
1592                if (pixel * ylab[gridind].lfac[i] >=  2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1593                   im->ygrid_scale.labfact =  ylab[gridind].lfac[i];
1594                   break;
1595                }                          
1596             } 
1597             
1598             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1599         }
1600     } else {
1601         im->ygrid_scale.gridstep = im->ygridstep;
1602         im->ygrid_scale.labfact = im->ylabfact;
1603     }
1604     return 1;
1605 }
1606
1607 int draw_horizontal_grid(image_desc_t *im)
1608 {
1609     int      i;
1610     double   scaledstep;
1611     char     graph_label[100];
1612     double X0=im->xorigin;
1613     double X1=im->xorigin+im->xsize;
1614    
1615     int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1616     int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1617     double MaxY;
1618     scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1619     MaxY = scaledstep*(double)egrid;
1620     for (i = sgrid; i <= egrid; i++){
1621        double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1622        if ( Y0 >= im->yorigin-im->ysize
1623                  && Y0 <= im->yorigin){       
1624             if(i % im->ygrid_scale.labfact == 0){               
1625                 if (im->symbol == ' ') {
1626                     if(im->extra_flags & ALTYGRID) {
1627                         sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1628                     } else {
1629                         if(MaxY < 10) {
1630                            sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1631                         } else {
1632                            sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1633                         }
1634                     }
1635                 }else {
1636                     char sisym = ( i == 0  ? ' ' : im->symbol);
1637                     if(im->extra_flags & ALTYGRID) {
1638                         sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1639                     } else {
1640                         if(MaxY < 10){
1641                           sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1642                         } else {
1643                           sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1644                         }
1645                     }
1646                 }
1647
1648                gfx_new_text ( im->canvas,
1649                               X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1650                               im->graph_col[GRC_FONT],
1651                               im->text_prop[TEXT_PROP_AXIS].font,
1652                               im->text_prop[TEXT_PROP_AXIS].size,
1653                               im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1654                               graph_label );
1655                gfx_new_dashed_line ( im->canvas,
1656                               X0-2,Y0,
1657                               X1+2,Y0,
1658                               MGRIDWIDTH, im->graph_col[GRC_MGRID],
1659                               im->grid_dash_on, im->grid_dash_off);            
1660                
1661             } else if (!(im->extra_flags & NOMINOR)) {          
1662                gfx_new_dashed_line ( im->canvas,
1663                               X0-1,Y0,
1664                               X1+1,Y0,
1665                               GRIDWIDTH, im->graph_col[GRC_GRID],
1666                               im->grid_dash_on, im->grid_dash_off);            
1667                
1668             }       
1669         }       
1670     } 
1671     return 1;
1672 }
1673
1674 /* logaritmic horizontal grid */
1675 int
1676 horizontal_log_grid(image_desc_t   *im)   
1677 {
1678     double   pixpex;
1679     int      ii,i;
1680     int      minoridx=0, majoridx=0;
1681     char     graph_label[100];
1682     double   X0,X1,Y0;   
1683     double   value, pixperstep, minstep;
1684
1685     /* find grid spaceing */
1686     pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1687
1688         if (isnan(pixpex)) {
1689                 return 0;
1690         }
1691
1692     for(i=0;yloglab[i][0] > 0;i++){
1693         minstep = log10(yloglab[i][0]);
1694         for(ii=1;yloglab[i][ii+1] > 0;ii++){
1695             if(yloglab[i][ii+2]==0){
1696                 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1697                 break;
1698             }
1699         }
1700         pixperstep = pixpex * minstep;
1701         if(pixperstep > 5){minoridx = i;}
1702        if(pixperstep > 2 *  im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1703     }
1704    
1705    X0=im->xorigin;
1706    X1=im->xorigin+im->xsize;
1707     /* paint minor grid */
1708     for (value = pow((double)10, log10(im->minval) 
1709                           - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1710          value  <= im->maxval;
1711          value *= yloglab[minoridx][0]){
1712         if (value < im->minval) continue;
1713         i=0;    
1714         while(yloglab[minoridx][++i] > 0){          
1715            Y0 = ytr(im,value * yloglab[minoridx][i]);
1716            if (Y0 <= im->yorigin - im->ysize) break;
1717            gfx_new_dashed_line ( im->canvas,
1718                           X0-1,Y0,
1719                           X1+1,Y0,
1720                           GRIDWIDTH, im->graph_col[GRC_GRID],
1721                           im->grid_dash_on, im->grid_dash_off);
1722         }
1723     }
1724
1725     /* paint major grid and labels*/
1726     for (value = pow((double)10, log10(im->minval) 
1727                           - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1728          value <= im->maxval;
1729          value *= yloglab[majoridx][0]){
1730         if (value < im->minval) continue;
1731         i=0;    
1732         while(yloglab[majoridx][++i] > 0){          
1733            Y0 = ytr(im,value * yloglab[majoridx][i]);    
1734            if (Y0 <= im->yorigin - im->ysize) break;
1735            gfx_new_dashed_line ( im->canvas,
1736                           X0-2,Y0,
1737                           X1+2,Y0,
1738                           MGRIDWIDTH, im->graph_col[GRC_MGRID],
1739                           im->grid_dash_on, im->grid_dash_off);
1740            
1741            sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1742            gfx_new_text ( im->canvas,
1743                           X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1744                           im->graph_col[GRC_FONT],
1745                           im->text_prop[TEXT_PROP_AXIS].font,
1746                           im->text_prop[TEXT_PROP_AXIS].size,
1747                           im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1748                           graph_label );
1749         } 
1750     }
1751         return 1;
1752 }
1753
1754
1755 void
1756 vertical_grid(
1757     image_desc_t   *im )
1758 {   
1759     int xlab_sel;               /* which sort of label and grid ? */
1760     time_t ti, tilab, timajor;
1761     long factor;
1762     char graph_label[100];
1763     double X0,Y0,Y1; /* points for filled graph and more*/
1764     struct tm tm;
1765
1766     /* the type of time grid is determined by finding
1767        the number of seconds per pixel in the graph */
1768     
1769     
1770     if(im->xlab_user.minsec == -1){
1771         factor=(im->end - im->start)/im->xsize;
1772         xlab_sel=0;
1773         while ( xlab[xlab_sel+1].minsec != -1 
1774                 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; }   /* pick the last one */
1775         while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
1776                 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; }       /* go back to the smallest size */
1777         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1778         im->xlab_user.gridst = xlab[xlab_sel].gridst;
1779         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1780         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1781         im->xlab_user.labtm = xlab[xlab_sel].labtm;
1782         im->xlab_user.labst = xlab[xlab_sel].labst;
1783         im->xlab_user.precis = xlab[xlab_sel].precis;
1784         im->xlab_user.stst = xlab[xlab_sel].stst;
1785     }
1786     
1787     /* y coords are the same for every line ... */
1788     Y0 = im->yorigin;
1789     Y1 = im->yorigin-im->ysize;
1790    
1791
1792     /* paint the minor grid */
1793     if (!(im->extra_flags & NOMINOR))
1794     {
1795         for(ti = find_first_time(im->start,
1796                                 im->xlab_user.gridtm,
1797                                 im->xlab_user.gridst),
1798             timajor = find_first_time(im->start,
1799                                 im->xlab_user.mgridtm,
1800                                 im->xlab_user.mgridst);
1801             ti < im->end; 
1802             ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1803             ){
1804             /* are we inside the graph ? */
1805             if (ti < im->start || ti > im->end) continue;
1806             while (timajor < ti) {
1807                 timajor = find_next_time(timajor,
1808                         im->xlab_user.mgridtm, im->xlab_user.mgridst);
1809             }
1810             if (ti == timajor) continue; /* skip as falls on major grid line */
1811            X0 = xtr(im,ti);       
1812            gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1813                im->graph_col[GRC_GRID],
1814                im->grid_dash_on, im->grid_dash_off);
1815            
1816         }
1817     }
1818
1819     /* paint the major grid */
1820     for(ti = find_first_time(im->start,
1821                             im->xlab_user.mgridtm,
1822                             im->xlab_user.mgridst);
1823         ti < im->end; 
1824         ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1825         ){
1826         /* are we inside the graph ? */
1827         if (ti < im->start || ti > im->end) continue;
1828        X0 = xtr(im,ti);
1829        gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1830            im->graph_col[GRC_MGRID],
1831            im->grid_dash_on, im->grid_dash_off);
1832        
1833     }
1834     /* paint the labels below the graph */
1835     for(ti = find_first_time(im->start - im->xlab_user.precis/2,
1836                             im->xlab_user.labtm,
1837                             im->xlab_user.labst);
1838         ti <= im->end - im->xlab_user.precis/2; 
1839         ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1840         ){
1841         tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1842         /* are we inside the graph ? */
1843         if (tilab < im->start || tilab > im->end) continue;
1844
1845 #if HAVE_STRFTIME
1846         localtime_r(&tilab, &tm);
1847         strftime(graph_label,99,im->xlab_user.stst, &tm);
1848 #else
1849 # error "your libc has no strftime I guess we'll abort the exercise here."
1850 #endif
1851        gfx_new_text ( im->canvas,
1852                       xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
1853                       im->graph_col[GRC_FONT],
1854                       im->text_prop[TEXT_PROP_AXIS].font,
1855                       im->text_prop[TEXT_PROP_AXIS].size,
1856                       im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
1857                       graph_label );
1858        
1859     }
1860
1861 }
1862
1863
1864 void 
1865 axis_paint(
1866    image_desc_t   *im
1867            )
1868 {   
1869     /* draw x and y axis */
1870     /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1871                       im->xorigin+im->xsize,im->yorigin-im->ysize,
1872                       GRIDWIDTH, im->graph_col[GRC_AXIS]);
1873        
1874        gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1875                          im->xorigin+im->xsize,im->yorigin-im->ysize,
1876                          GRIDWIDTH, im->graph_col[GRC_AXIS]); */
1877    
1878        gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1879                          im->xorigin+im->xsize+4,im->yorigin,
1880                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
1881    
1882        gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1883                          im->xorigin,im->yorigin-im->ysize-4,
1884                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
1885    
1886     
1887     /* arrow for X and Y axis direction */
1888     gfx_new_area ( im->canvas, 
1889                    im->xorigin+im->xsize+2,  im->yorigin-2,
1890                    im->xorigin+im->xsize+2,  im->yorigin+3,
1891                    im->xorigin+im->xsize+7,  im->yorigin+0.5, /* LINEOFFSET */
1892                    im->graph_col[GRC_ARROW]);
1893
1894     gfx_new_area ( im->canvas, 
1895                    im->xorigin-2,  im->yorigin-im->ysize-2,
1896                    im->xorigin+3,  im->yorigin-im->ysize-2,
1897                    im->xorigin+0.5,    im->yorigin-im->ysize-7, /* LINEOFFSET */
1898                    im->graph_col[GRC_ARROW]);
1899
1900 }
1901
1902 void
1903 grid_paint(image_desc_t   *im)
1904 {   
1905     long i;
1906     int res=0;
1907     double X0,Y0; /* points for filled graph and more*/
1908     gfx_node_t *node;
1909
1910     /* draw 3d border */
1911     node = gfx_new_area (im->canvas, 0,im->yimg,
1912                                  2,im->yimg-2,
1913                                  2,2,im->graph_col[GRC_SHADEA]);
1914     gfx_add_point( node , im->ximg - 2, 2 );
1915     gfx_add_point( node , im->ximg, 0 );
1916     gfx_add_point( node , 0,0 );
1917 /*    gfx_add_point( node , 0,im->yimg ); */
1918    
1919     node =  gfx_new_area (im->canvas, 2,im->yimg-2,
1920                                   im->ximg-2,im->yimg-2,
1921                                   im->ximg - 2, 2,
1922                                  im->graph_col[GRC_SHADEB]);
1923     gfx_add_point( node ,   im->ximg,0);
1924     gfx_add_point( node ,   im->ximg,im->yimg);
1925     gfx_add_point( node ,   0,im->yimg);
1926 /*    gfx_add_point( node , 0,im->yimg ); */
1927    
1928    
1929     if (im->draw_x_grid == 1 )
1930       vertical_grid(im);
1931     
1932     if (im->draw_y_grid == 1){
1933         if(im->logarithmic){
1934                 res = horizontal_log_grid(im);
1935         } else {
1936                 res = draw_horizontal_grid(im);
1937         }
1938         
1939         /* dont draw horizontal grid if there is no min and max val */
1940         if (! res ) {
1941           char *nodata = "No Data found";
1942            gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1943                         im->graph_col[GRC_FONT],
1944                         im->text_prop[TEXT_PROP_AXIS].font,
1945                         im->text_prop[TEXT_PROP_AXIS].size,
1946                         im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1947                         nodata );          
1948         }
1949     }
1950
1951     /* yaxis unit description */
1952     gfx_new_text( im->canvas,
1953                   10, (im->yorigin - im->ysize/2),
1954                   im->graph_col[GRC_FONT],
1955                   im->text_prop[TEXT_PROP_UNIT].font,
1956                   im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth, 
1957                   RRDGRAPH_YLEGEND_ANGLE,
1958                   GFX_H_LEFT, GFX_V_CENTER,
1959                   im->ylegend);
1960
1961     /* graph title */
1962     gfx_new_text( im->canvas,
1963                   im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
1964                   im->graph_col[GRC_FONT],
1965                   im->text_prop[TEXT_PROP_TITLE].font,
1966                   im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1967                   GFX_H_CENTER, GFX_V_CENTER,
1968                   im->title);
1969     /* rrdtool 'logo' */
1970     gfx_new_text( im->canvas,
1971                   im->ximg-7, 7,
1972                   ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
1973                   im->text_prop[TEXT_PROP_AXIS].font,
1974                   5.5, im->tabwidth, 270,
1975                   GFX_H_RIGHT, GFX_V_TOP,
1976                   "RRDTOOL / TOBI OETIKER");
1977     
1978     /* graph labels */
1979     if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1980             for(i=0;i<im->gdes_c;i++){
1981                     if(im->gdes[i].legend[0] =='\0')
1982                             continue;
1983                     
1984                     /* im->gdes[i].leg_y is the bottom of the legend */
1985                     X0 = im->gdes[i].leg_x;
1986                     Y0 = im->gdes[i].leg_y;
1987                     gfx_new_text ( im->canvas, X0, Y0,
1988                                    im->graph_col[GRC_FONT],
1989                                    im->text_prop[TEXT_PROP_LEGEND].font,
1990                                    im->text_prop[TEXT_PROP_LEGEND].size,
1991                                    im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1992                                    im->gdes[i].legend );
1993                     /* The legend for GRAPH items starts with "M " to have
1994                        enough space for the box */
1995                     if (           im->gdes[i].gf != GF_PRINT &&
1996                                    im->gdes[i].gf != GF_GPRINT &&
1997                                    im->gdes[i].gf != GF_COMMENT) {
1998                             int boxH, boxV;
1999                             
2000                             boxH = gfx_get_text_width(im->canvas, 0,
2001                                                       im->text_prop[TEXT_PROP_LEGEND].font,
2002                                                       im->text_prop[TEXT_PROP_LEGEND].size,
2003                                                       im->tabwidth,"o", 0) * 1.2;
2004                             boxV = boxH*1.1;
2005                             
2006                             /* make sure transparent colors show up the same way as in the graph */
2007                              node = gfx_new_area(im->canvas,
2008                                                 X0,Y0-boxV,
2009                                                 X0,Y0,
2010                                                 X0+boxH,Y0,
2011                                                 im->graph_col[GRC_BACK]);
2012                             gfx_add_point ( node, X0+boxH, Y0-boxV );
2013
2014                             node = gfx_new_area(im->canvas,
2015                                                 X0,Y0-boxV,
2016                                                 X0,Y0,
2017                                                 X0+boxH,Y0,
2018                                                 im->gdes[i].col);
2019                             gfx_add_point ( node, X0+boxH, Y0-boxV );
2020                             node = gfx_new_line(im->canvas,
2021                                                 X0,Y0-boxV,
2022                                                 X0,Y0,
2023                                                 1.0,im->graph_col[GRC_FRAME]);
2024                             gfx_add_point(node,X0+boxH,Y0);
2025                             gfx_add_point(node,X0+boxH,Y0-boxV);
2026                             gfx_close_path(node);
2027                     }
2028             }
2029     }
2030 }
2031
2032
2033 /*****************************************************
2034  * lazy check make sure we rely need to create this graph
2035  *****************************************************/
2036
2037 int lazy_check(image_desc_t *im){
2038     FILE *fd = NULL;
2039         int size = 1;
2040     struct stat  imgstat;
2041     
2042     if (im->lazy == 0) return 0; /* no lazy option */
2043     if (stat(im->graphfile,&imgstat) != 0) 
2044       return 0; /* can't stat */
2045     /* one pixel in the existing graph is more then what we would
2046        change here ... */
2047     if (time(NULL) - imgstat.st_mtime > 
2048         (im->end - im->start) / im->xsize) 
2049       return 0;
2050     if ((fd = fopen(im->graphfile,"rb")) == NULL) 
2051       return 0; /* the file does not exist */
2052     switch (im->canvas->imgformat) {
2053     case IF_PNG:
2054            size = PngSize(fd,&(im->ximg),&(im->yimg));
2055            break;
2056     default:
2057            size = 1;
2058     }
2059     fclose(fd);
2060     return size;
2061 }
2062
2063 #ifdef WITH_PIECHART
2064 void
2065 pie_part(image_desc_t *im, gfx_color_t color,
2066             double PieCenterX, double PieCenterY, double Radius,
2067             double startangle, double endangle)
2068 {
2069     gfx_node_t *node;
2070     double angle;
2071     double step=M_PI/50; /* Number of iterations for the circle;
2072                          ** 10 is definitely too low, more than
2073                          ** 50 seems to be overkill
2074                          */
2075
2076     /* Strange but true: we have to work clockwise or else
2077     ** anti aliasing nor transparency don't work.
2078     **
2079     ** This test is here to make sure we do it right, also
2080     ** this makes the for...next loop more easy to implement.
2081     ** The return will occur if the user enters a negative number
2082     ** (which shouldn't be done according to the specs) or if the
2083     ** programmers do something wrong (which, as we all know, never
2084     ** happens anyway :)
2085     */
2086     if (endangle<startangle) return;
2087
2088     /* Hidden feature: Radius decreases each full circle */
2089     angle=startangle;
2090     while (angle>=2*M_PI) {
2091         angle  -= 2*M_PI;
2092         Radius *= 0.8;
2093     }
2094
2095     node=gfx_new_area(im->canvas,
2096                 PieCenterX+sin(startangle)*Radius,
2097                 PieCenterY-cos(startangle)*Radius,
2098                 PieCenterX,
2099                 PieCenterY,
2100                 PieCenterX+sin(endangle)*Radius,
2101                 PieCenterY-cos(endangle)*Radius,
2102                 color);
2103     for (angle=endangle;angle-startangle>=step;angle-=step) {
2104         gfx_add_point(node,
2105                 PieCenterX+sin(angle)*Radius,
2106                 PieCenterY-cos(angle)*Radius );
2107     }
2108 }
2109
2110 #endif
2111
2112 int
2113 graph_size_location(image_desc_t *im, int elements
2114
2115 #ifdef WITH_PIECHART
2116 , int piechart
2117 #endif
2118
2119  )
2120 {
2121     /* The actual size of the image to draw is determined from
2122     ** several sources.  The size given on the command line is
2123     ** the graph area but we need more as we have to draw labels
2124     ** and other things outside the graph area
2125     */
2126
2127     /* +-+-------------------------------------------+
2128     ** |l|.................title.....................|
2129     ** |e+--+-------------------------------+--------+
2130     ** |b| b|                               |        |
2131     ** |a| a|                               |  pie   |
2132     ** |l| l|          main graph area      | chart  |
2133     ** |.| .|                               |  area  |
2134     ** |t| y|                               |        |
2135     ** |r+--+-------------------------------+--------+
2136     ** |e|  | x-axis labels                 |        |
2137     ** |v+--+-------------------------------+--------+
2138     ** | |..............legends......................|
2139     ** +-+-------------------------------------------+
2140     */
2141     int Xvertical=0,    
2142                         Ytitle   =0,
2143         Xylabel  =0,    
2144         Xmain    =0,    Ymain    =0,
2145 #ifdef WITH_PIECHART
2146         Xpie     =0,    Ypie     =0,
2147 #endif
2148                         Yxlabel  =0,
2149 #if 0
2150         Xlegend  =0,    Ylegend  =0,
2151 #endif
2152         Xspacing =15,  Yspacing =15;
2153
2154     if (im->extra_flags & ONLY_GRAPH) {
2155         im->xorigin =0;
2156         im->ximg = im->xsize;
2157         im->yimg = im->ysize;
2158         im->yorigin = im->ysize;
2159         ytr(im,DNAN); 
2160         return 0;
2161     }
2162
2163     if (im->ylegend[0] != '\0' ) {
2164            Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2165     }
2166
2167
2168     if (im->title[0] != '\0') {
2169         /* The title is placed "inbetween" two text lines so it
2170         ** automatically has some vertical spacing.  The horizontal
2171         ** spacing is added here, on each side.
2172         */
2173         /* don't care for the with of the title
2174                 Xtitle = gfx_get_text_width(im->canvas, 0,
2175                 im->text_prop[TEXT_PROP_TITLE].font,
2176                 im->text_prop[TEXT_PROP_TITLE].size,
2177                 im->tabwidth,
2178                 im->title, 0) + 2*Xspacing; */
2179         Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2180     }
2181
2182     if (elements) {
2183         Xmain=im->xsize;
2184         Ymain=im->ysize;
2185         if (im->draw_x_grid) {
2186             Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2187         }
2188         if (im->draw_y_grid) {
2189             Xylabel=gfx_get_text_width(im->canvas, 0,
2190                         im->text_prop[TEXT_PROP_AXIS].font,
2191                         im->text_prop[TEXT_PROP_AXIS].size,
2192                         im->tabwidth,
2193                         "0", 0) * im->unitslength;
2194         }
2195     }
2196
2197 #ifdef WITH_PIECHART
2198     if (piechart) {
2199         im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2200         Xpie=im->piesize;
2201         Ypie=im->piesize;
2202     }
2203 #endif
2204
2205     /* Now calculate the total size.  Insert some spacing where
2206        desired.  im->xorigin and im->yorigin need to correspond
2207        with the lower left corner of the main graph area or, if
2208        this one is not set, the imaginary box surrounding the
2209        pie chart area. */
2210
2211     /* The legend width cannot yet be determined, as a result we
2212     ** have problems adjusting the image to it.  For now, we just
2213     ** forget about it at all; the legend will have to fit in the
2214     ** size already allocated.
2215     */
2216     im->ximg = Xylabel + Xmain + 2 * Xspacing;
2217
2218 #ifdef WITH_PIECHART
2219     im->ximg  += Xpie;
2220 #endif
2221
2222     if (Xmain) im->ximg += Xspacing;
2223 #ifdef WITH_PIECHART
2224     if (Xpie) im->ximg += Xspacing;
2225 #endif
2226
2227     im->xorigin = Xspacing + Xylabel;
2228
2229     /* the length of the title should not influence with width of the graph
2230        if (Xtitle > im->ximg) im->ximg = Xtitle; */
2231
2232     if (Xvertical) { /* unit description */
2233         im->ximg += Xvertical;
2234         im->xorigin += Xvertical;
2235     }
2236     xtr(im,0);
2237
2238     /* The vertical size is interesting... we need to compare
2239     ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2240     ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2241     ** start even thinking about Ylegend.
2242     **
2243     ** Do it in three portions: First calculate the inner part,
2244     ** then do the legend, then adjust the total height of the img.
2245     */
2246
2247     /* reserve space for main and/or pie */
2248
2249     im->yimg = Ymain + Yxlabel;
2250     
2251 #ifdef WITH_PIECHART
2252     if (im->yimg < Ypie) im->yimg = Ypie;
2253 #endif
2254
2255     im->yorigin = im->yimg - Yxlabel;
2256
2257     /* reserve space for the title *or* some padding above the graph */
2258     if (Ytitle) {
2259         im->yimg += Ytitle;
2260         im->yorigin += Ytitle;
2261     } else {
2262         im->yimg += 1.5*Yspacing;
2263         im->yorigin += 1.5*Yspacing;
2264     }
2265     /* reserve space for padding below the graph */
2266     im->yimg += Yspacing;
2267      
2268     /* Determine where to place the legends onto the image.
2269     ** Adjust im->yimg to match the space requirements.
2270     */
2271     if(leg_place(im)==-1)
2272         return -1;
2273
2274
2275 #if 0
2276     if (Xlegend > im->ximg) {
2277         im->ximg = Xlegend;
2278         /* reposition Pie */
2279     }
2280 #endif
2281
2282 #ifdef WITH_PIECHART
2283     /* The pie is placed in the upper right hand corner,
2284     ** just below the title (if any) and with sufficient
2285     ** padding.
2286     */
2287     if (elements) {
2288         im->pie_x = im->ximg - Xspacing - Xpie/2;
2289         im->pie_y = im->yorigin-Ymain+Ypie/2;
2290     } else {
2291         im->pie_x = im->ximg/2;
2292         im->pie_y = im->yorigin-Ypie/2;
2293     }
2294 #endif
2295
2296     ytr(im,DNAN);
2297     return 0;
2298 }
2299
2300 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2301 /* yes we are loosing precision by doing tos with floats instead of doubles
2302    but it seems more stable this way. */
2303    
2304 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
2305 {
2306
2307     int aInt = *(int*)&A;
2308     int bInt = *(int*)&B;
2309     int intDiff;
2310     /* Make sure maxUlps is non-negative and small enough that the
2311        default NAN won't compare as equal to anything.  */
2312
2313     /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
2314
2315     /* Make aInt lexicographically ordered as a twos-complement int */
2316
2317     if (aInt < 0)
2318         aInt = 0x80000000l - aInt;
2319
2320     /* Make bInt lexicographically ordered as a twos-complement int */
2321
2322     if (bInt < 0)
2323         bInt = 0x80000000l - bInt;
2324
2325     intDiff = abs(aInt - bInt);
2326
2327     if (intDiff <= maxUlps)
2328         return 1;
2329
2330     return 0;
2331 }
2332
2333 /* draw that picture thing ... */
2334 int
2335 graph_paint(image_desc_t *im, char ***calcpr)
2336 {
2337   int i,ii;
2338   int lazy =     lazy_check(im);
2339 #ifdef WITH_PIECHART
2340   int piechart = 0;
2341   double PieStart=0.0;
2342 #endif
2343   FILE  *fo;
2344   gfx_node_t *node;
2345   
2346   double areazero = 0.0;
2347   enum gf_en stack_gf = GF_PRINT;
2348   graph_desc_t *lastgdes = NULL;    
2349
2350   /* if we are lazy and there is nothing to PRINT ... quit now */
2351   if (lazy && im->prt_c==0) return 0;
2352
2353   /* pull the data from the rrd files ... */
2354   
2355   if(data_fetch(im)==-1)
2356     return -1;
2357
2358   /* evaluate VDEF and CDEF operations ... */
2359   if(data_calc(im)==-1)
2360     return -1;
2361
2362 #ifdef WITH_PIECHART  
2363   /* check if we need to draw a piechart */
2364   for(i=0;i<im->gdes_c;i++){
2365     if (im->gdes[i].gf == GF_PART) {
2366       piechart=1;
2367       break;
2368     }
2369   }
2370 #endif
2371
2372   /* calculate and PRINT and GPRINT definitions. We have to do it at
2373    * this point because it will affect the length of the legends
2374    * if there are no graph elements we stop here ... 
2375    * if we are lazy, try to quit ... 
2376    */
2377   i=print_calc(im,calcpr);
2378   if(i<0) return -1;
2379   if(((i==0)
2380 #ifdef WITH_PIECHART
2381 &&(piechart==0)
2382 #endif
2383 ) || lazy) return 0;
2384
2385 #ifdef WITH_PIECHART
2386   /* If there's only the pie chart to draw, signal this */
2387   if (i==0) piechart=2;
2388 #endif
2389   
2390   /* get actual drawing data and find min and max values*/
2391   if(data_proc(im)==-1)
2392     return -1;
2393   
2394   if(!im->logarithmic){si_unit(im);}        /* identify si magnitude Kilo, Mega Giga ? */
2395   
2396   if(!im->rigid && ! im->logarithmic)
2397     expand_range(im);   /* make sure the upper and lower limit are
2398                            sensible values */
2399
2400   if (!calc_horizontal_grid(im))
2401     return -1;
2402
2403   if (im->gridfit)
2404     apply_gridfit(im);
2405
2406
2407 /**************************************************************
2408  *** Calculating sizes and locations became a bit confusing ***
2409  *** so I moved this into a separate function.              ***
2410  **************************************************************/
2411   if(graph_size_location(im,i
2412 #ifdef WITH_PIECHART
2413 ,piechart
2414 #endif
2415 )==-1)
2416     return -1;
2417
2418   /* the actual graph is created by going through the individual
2419      graph elements and then drawing them */
2420   
2421   node=gfx_new_area ( im->canvas,
2422                       0, 0,
2423                       0, im->yimg,
2424                       im->ximg, im->yimg,                      
2425                       im->graph_col[GRC_BACK]);
2426
2427   gfx_add_point(node,im->ximg, 0);
2428
2429 #ifdef WITH_PIECHART
2430   if (piechart != 2) {
2431 #endif
2432     node=gfx_new_area ( im->canvas,
2433                       im->xorigin,             im->yorigin, 
2434                       im->xorigin + im->xsize, im->yorigin,
2435                       im->xorigin + im->xsize, im->yorigin-im->ysize,
2436                       im->graph_col[GRC_CANVAS]);
2437   
2438     gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2439
2440     if (im->minval > 0.0)
2441       areazero = im->minval;
2442     if (im->maxval < 0.0)
2443       areazero = im->maxval;
2444 #ifdef WITH_PIECHART
2445    }
2446 #endif
2447
2448 #ifdef WITH_PIECHART
2449   if (piechart) {
2450     pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2451   }
2452 #endif
2453
2454   for(i=0;i<im->gdes_c;i++){
2455     switch(im->gdes[i].gf){
2456     case GF_CDEF:
2457     case GF_VDEF:
2458     case GF_DEF:
2459     case GF_PRINT:
2460     case GF_GPRINT:
2461     case GF_COMMENT:
2462     case GF_HRULE:
2463     case GF_VRULE:
2464     case GF_XPORT:
2465     case GF_SHIFT:
2466       break;
2467     case GF_TICK:
2468       for (ii = 0; ii < im->xsize; ii++)
2469         {
2470           if (!isnan(im->gdes[i].p_data[ii]) && 
2471               im->gdes[i].p_data[ii] > 0.0)
2472             { 
2473               /* generate a tick */
2474               gfx_new_line(im->canvas, im -> xorigin + ii, 
2475                            im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2476                            im -> xorigin + ii, 
2477                            im -> yorigin,
2478                            1.0,
2479                            im -> gdes[i].col );
2480             }
2481         }
2482       break;
2483     case GF_LINE:
2484     case GF_AREA:
2485       stack_gf = im->gdes[i].gf;
2486     case GF_STACK:          
2487       /* fix data points at oo and -oo */
2488       for(ii=0;ii<im->xsize;ii++){
2489         if (isinf(im->gdes[i].p_data[ii])){
2490           if (im->gdes[i].p_data[ii] > 0) {
2491             im->gdes[i].p_data[ii] = im->maxval ;
2492           } else {
2493             im->gdes[i].p_data[ii] = im->minval ;
2494           }                 
2495           
2496         }
2497       } /* for */
2498
2499       /* *******************************************************
2500        a           ___. (a,t) 
2501                   |   |    ___
2502               ____|   |   |   |
2503               |       |___|
2504        -------|--t-1--t--------------------------------      
2505                       
2506       if we know the value at time t was a then 
2507       we draw a square from t-1 to t with the value a.
2508
2509       ********************************************************* */
2510       if (im->gdes[i].col != 0x0){   
2511         /* GF_LINE and friend */
2512         if(stack_gf == GF_LINE ){
2513           double last_y=0.0;
2514           node = NULL;
2515           for(ii=1;ii<im->xsize;ii++){
2516             if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2517                 node = NULL;
2518                 continue;
2519             }
2520             if ( node == NULL ) {
2521                 last_y = ytr(im,im->gdes[i].p_data[ii]);
2522                 if ( im->slopemode == 0 ){
2523                   node = gfx_new_line(im->canvas,
2524                                     ii-1+im->xorigin,last_y,
2525                                     ii+im->xorigin,last_y,
2526                                     im->gdes[i].linewidth,
2527                                     im->gdes[i].col);
2528                 } else {
2529                   node = gfx_new_line(im->canvas,
2530                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2531                                     ii+im->xorigin,last_y,
2532                                     im->gdes[i].linewidth,
2533                                     im->gdes[i].col);
2534                 }
2535              } else {
2536                double new_y = ytr(im,im->gdes[i].p_data[ii]);
2537                if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2538                    gfx_add_point(node,ii-1+im->xorigin,new_y);
2539                };
2540                last_y = new_y;
2541                gfx_add_point(node,ii+im->xorigin,new_y);
2542              };
2543
2544           }
2545         } else {
2546           int idxI=-1;
2547           double *foreY=malloc(sizeof(double)*im->xsize*2);
2548           double *foreX=malloc(sizeof(double)*im->xsize*2);
2549           double *backY=malloc(sizeof(double)*im->xsize*2);
2550           double *backX=malloc(sizeof(double)*im->xsize*2);
2551           int drawem = 0;
2552           for(ii=0;ii<=im->xsize;ii++){
2553             double ybase,ytop;
2554             if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2555                int cntI=1;
2556                int lastI=0;
2557                while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2558                node = gfx_new_area(im->canvas,
2559                                 backX[0],backY[0],
2560                                 foreX[0],foreY[0],
2561                                 foreX[cntI],foreY[cntI], im->gdes[i].col);
2562                while (cntI < idxI) {
2563                  lastI = cntI;
2564                  cntI++;
2565                  while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;} 
2566                  gfx_add_point(node,foreX[cntI],foreY[cntI]);
2567                }
2568                gfx_add_point(node,backX[idxI],backY[idxI]);
2569                while (idxI > 1){
2570                  lastI = idxI;
2571                  idxI--;
2572                  while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;} 
2573                  gfx_add_point(node,backX[idxI],backY[idxI]);
2574                }
2575                idxI=-1;
2576                drawem = 0;
2577             }
2578             if (drawem != 0){
2579               drawem = 0;
2580               idxI=-1;
2581             }
2582             if (ii == im->xsize) break;
2583             
2584             /* keep things simple for now, just draw these bars
2585                do not try to build a big and complex area */
2586
2587                                                               
2588             if ( im->slopemode == 0 && ii==0){
2589                 continue;
2590             }
2591             if ( isnan(im->gdes[i].p_data[ii]) ) {
2592                 drawem = 1;
2593                 continue;
2594             }
2595             ytop = ytr(im,im->gdes[i].p_data[ii]);
2596             if ( lastgdes && im->gdes[i].stack ) {
2597                   ybase = ytr(im,lastgdes->p_data[ii]);
2598             } else {
2599                   ybase = ytr(im,areazero);
2600             }
2601             if ( ybase == ytop ){
2602                 drawem = 1;
2603                 continue;       
2604             }
2605             /* every area has to be wound clock-wise,
2606                so we have to make sur base remains base  */             
2607             if (ybase > ytop){
2608                 double extra = ytop;
2609                 ytop = ybase;
2610                 ybase = extra;
2611             }
2612             if ( im->slopemode == 0 ){
2613                     backY[++idxI] = ybase-0.2;
2614                     backX[idxI] = ii+im->xorigin-1;
2615                     foreY[idxI] = ytop+0.2;
2616                     foreX[idxI] = ii+im->xorigin-1;
2617             }
2618             backY[++idxI] = ybase-0.2;
2619             backX[idxI] = ii+im->xorigin;
2620             foreY[idxI] = ytop+0.2;
2621             foreX[idxI] = ii+im->xorigin;
2622           }
2623           /* close up any remaining area */             
2624           free(foreY);
2625           free(foreX);
2626           free(backY);
2627           free(backX);
2628         } /* else GF_LINE */
2629       } /* if color != 0x0 */
2630       /* make sure we do not run into trouble when stacking on NaN */
2631       for(ii=0;ii<im->xsize;ii++){
2632         if (isnan(im->gdes[i].p_data[ii])) {
2633           if (lastgdes && (im->gdes[i].stack)) {
2634             im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2635           } else {
2636             im->gdes[i].p_data[ii] = areazero;
2637           }
2638         }
2639       } 
2640       lastgdes = &(im->gdes[i]);                         
2641       break;
2642 #ifdef WITH_PIECHART
2643     case GF_PART:
2644       if(isnan(im->gdes[i].yrule)) /* fetch variable */
2645         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2646      
2647       if (finite(im->gdes[i].yrule)) {  /* even the fetched var can be NaN */
2648         pie_part(im,im->gdes[i].col,
2649                 im->pie_x,im->pie_y,im->piesize*0.4,
2650                 M_PI*2.0*PieStart/100.0,
2651                 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2652         PieStart += im->gdes[i].yrule;
2653       }
2654       break;
2655 #endif
2656         
2657     } /* switch */
2658   }
2659 #ifdef WITH_PIECHART
2660   if (piechart==2) {
2661     im->draw_x_grid=0;
2662     im->draw_y_grid=0;
2663   }
2664 #endif
2665
2666
2667   /* grid_paint also does the text */
2668   if( !(im->extra_flags & ONLY_GRAPH) )  
2669     grid_paint(im);
2670
2671   
2672   if( !(im->extra_flags & ONLY_GRAPH) )  
2673       axis_paint(im);
2674   
2675   /* the RULES are the last thing to paint ... */
2676   for(i=0;i<im->gdes_c;i++){    
2677     
2678     switch(im->gdes[i].gf){
2679     case GF_HRULE:
2680       if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2681         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2682       };
2683       if(im->gdes[i].yrule >= im->minval
2684          && im->gdes[i].yrule <= im->maxval)
2685         gfx_new_line(im->canvas,
2686                      im->xorigin,ytr(im,im->gdes[i].yrule),
2687                      im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2688                      1.0,im->gdes[i].col); 
2689       break;
2690     case GF_VRULE:
2691       if(im->gdes[i].xrule == 0) { /* fetch variable */
2692         im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2693       };
2694       if(im->gdes[i].xrule >= im->start
2695          && im->gdes[i].xrule <= im->end)
2696         gfx_new_line(im->canvas,
2697                      xtr(im,im->gdes[i].xrule),im->yorigin,
2698                      xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2699                      1.0,im->gdes[i].col); 
2700       break;
2701     default:
2702       break;
2703     }
2704   }
2705
2706   
2707   if (strcmp(im->graphfile,"-")==0) {
2708     fo = im->graphhandle ? im->graphhandle : stdout;
2709 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2710     /* Change translation mode for stdout to BINARY */
2711     _setmode( _fileno( fo ), O_BINARY );
2712 #endif
2713   } else {
2714     if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2715       rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2716                     rrd_strerror(errno));
2717       return (-1);
2718     }
2719   }
2720   gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2721   if (strcmp(im->graphfile,"-") != 0)
2722     fclose(fo);
2723   return 0;
2724 }
2725
2726
2727 /*****************************************************
2728  * graph stuff 
2729  *****************************************************/
2730
2731 int
2732 gdes_alloc(image_desc_t *im){
2733
2734     im->gdes_c++;
2735     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2736                                            * sizeof(graph_desc_t)))==NULL){
2737         rrd_set_error("realloc graph_descs");
2738         return -1;
2739     }
2740
2741
2742     im->gdes[im->gdes_c-1].step=im->step;
2743     im->gdes[im->gdes_c-1].step_orig=im->step;
2744     im->gdes[im->gdes_c-1].stack=0;
2745     im->gdes[im->gdes_c-1].debug=0;
2746     im->gdes[im->gdes_c-1].start=im->start; 
2747     im->gdes[im->gdes_c-1].end=im->end; 
2748     im->gdes[im->gdes_c-1].vname[0]='\0'; 
2749     im->gdes[im->gdes_c-1].data=NULL;
2750     im->gdes[im->gdes_c-1].ds_namv=NULL;
2751     im->gdes[im->gdes_c-1].data_first=0;
2752     im->gdes[im->gdes_c-1].p_data=NULL;
2753     im->gdes[im->gdes_c-1].rpnp=NULL;
2754     im->gdes[im->gdes_c-1].shift=0;
2755     im->gdes[im->gdes_c-1].col = 0x0;
2756     im->gdes[im->gdes_c-1].legend[0]='\0';
2757     im->gdes[im->gdes_c-1].format[0]='\0';
2758     im->gdes[im->gdes_c-1].rrd[0]='\0';
2759     im->gdes[im->gdes_c-1].ds=-1;    
2760     im->gdes[im->gdes_c-1].p_data=NULL;    
2761     im->gdes[im->gdes_c-1].yrule=DNAN;
2762     im->gdes[im->gdes_c-1].xrule=0;
2763     return 0;
2764 }
2765
2766 /* copies input untill the first unescaped colon is found
2767    or until input ends. backslashes have to be escaped as well */
2768 int
2769 scan_for_col(const char *const input, int len, char *const output)
2770 {
2771     int inp,outp=0;
2772     for (inp=0; 
2773          inp < len &&
2774            input[inp] != ':' &&
2775            input[inp] != '\0';
2776          inp++){
2777       if (input[inp] == '\\' &&
2778           input[inp+1] != '\0' && 
2779           (input[inp+1] == '\\' ||
2780            input[inp+1] == ':')){
2781         output[outp++] = input[++inp];
2782       }
2783       else {
2784         output[outp++] = input[inp];
2785       }
2786     }
2787     output[outp] = '\0';
2788     return inp;
2789 }
2790 /* Some surgery done on this function, it became ridiculously big.
2791 ** Things moved:
2792 ** - initializing     now in rrd_graph_init()
2793 ** - options parsing  now in rrd_graph_options()
2794 ** - script parsing   now in rrd_graph_script()
2795 */
2796 int 
2797 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
2798 {
2799     image_desc_t   im;
2800     rrd_graph_init(&im);
2801     im.graphhandle = stream;
2802     
2803     rrd_graph_options(argc,argv,&im);
2804     if (rrd_test_error()) {
2805         im_free(&im);
2806         return -1;
2807     }
2808     
2809     if (strlen(argv[optind])>=MAXPATH) {
2810         rrd_set_error("filename (including path) too long");
2811         im_free(&im);
2812         return -1;
2813     }
2814     strncpy(im.graphfile,argv[optind],MAXPATH-1);
2815     im.graphfile[MAXPATH-1]='\0';
2816
2817     rrd_graph_script(argc,argv,&im,1);
2818     if (rrd_test_error()) {
2819         im_free(&im);
2820         return -1;
2821     }
2822
2823     /* Everything is now read and the actual work can start */
2824
2825     (*prdata)=NULL;
2826     if (graph_paint(&im,prdata)==-1){
2827         im_free(&im);
2828         return -1;
2829     }
2830
2831     /* The image is generated and needs to be output.
2832     ** Also, if needed, print a line with information about the image.
2833     */
2834
2835     *xsize=im.ximg;
2836     *ysize=im.yimg;
2837     *ymin=im.minval;
2838     *ymax=im.maxval;
2839     if (im.imginfo) {
2840         char *filename;
2841         if (!(*prdata)) {
2842             /* maybe prdata is not allocated yet ... lets do it now */
2843             if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2844                 rrd_set_error("malloc imginfo");
2845                 return -1; 
2846             };
2847         }
2848         if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2849          ==NULL){
2850             rrd_set_error("malloc imginfo");
2851             return -1;
2852         }
2853         filename=im.graphfile+strlen(im.graphfile);
2854         while(filename > im.graphfile) {
2855             if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2856             filename--;
2857         }
2858
2859         sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2860     }
2861     im_free(&im);
2862     return 0;
2863 }
2864
2865 void
2866 rrd_graph_init(image_desc_t *im)
2867 {
2868     unsigned int i;
2869
2870 #ifdef HAVE_TZSET
2871     tzset();
2872 #endif
2873 #ifdef HAVE_SETLOCALE
2874     setlocale(LC_TIME,"");
2875 #endif
2876     im->yorigin=0;
2877     im->xorigin=0;
2878     im->minval=0;
2879     im->xlab_user.minsec = -1;
2880     im->ximg=0;
2881     im->yimg=0;
2882     im->xsize = 400;
2883     im->ysize = 100;
2884     im->step = 0;
2885     im->ylegend[0] = '\0';
2886     im->title[0] = '\0';
2887     im->minval = DNAN;
2888     im->maxval = DNAN;    
2889     im->unitsexponent= 9999;
2890     im->unitslength= 6; 
2891     im->symbol = ' ';
2892     im->viewfactor = 1.0;
2893     im->extra_flags= 0;
2894     im->rigid = 0;
2895     im->gridfit = 1;
2896     im->imginfo = NULL;
2897     im->lazy = 0;
2898     im->slopemode = 0;
2899     im->logarithmic = 0;
2900     im->ygridstep = DNAN;
2901     im->draw_x_grid = 1;
2902     im->draw_y_grid = 1;
2903     im->base = 1000;
2904     im->prt_c = 0;
2905     im->gdes_c = 0;
2906     im->gdes = NULL;
2907     im->canvas = gfx_new_canvas();
2908     im->grid_dash_on = 1;
2909     im->grid_dash_off = 1;
2910     im->tabwidth = 40.0;
2911     
2912     for(i=0;i<DIM(graph_col);i++)
2913         im->graph_col[i]=graph_col[i];
2914
2915 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2916     {
2917             char *windir; 
2918             char rrd_win_default_font[1000];
2919             windir = getenv("windir");
2920             /* %windir% is something like D:\windows or C:\winnt */
2921             if (windir != NULL) {
2922                     strncpy(rrd_win_default_font,windir,999);
2923                     rrd_win_default_font[999] = '\0';
2924                     strcat(rrd_win_default_font,"\\fonts\\");
2925                     strcat(rrd_win_default_font,RRD_DEFAULT_FONT);         
2926                     for(i=0;i<DIM(text_prop);i++){
2927                             strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
2928                             text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
2929                      }
2930              }
2931     }
2932 #endif
2933     {
2934             char *deffont; 
2935             deffont = getenv("RRD_DEFAULT_FONT");
2936             if (deffont != NULL) {
2937                  for(i=0;i<DIM(text_prop);i++){
2938                         strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
2939                         text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
2940                  }
2941             }
2942     }
2943     for(i=0;i<DIM(text_prop);i++){        
2944       im->text_prop[i].size = text_prop[i].size;
2945       strcpy(im->text_prop[i].font,text_prop[i].font);
2946     }
2947 }
2948
2949 void
2950 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2951 {
2952     int                 stroff;    
2953     char                *parsetime_error = NULL;
2954     char                scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2955     time_t              start_tmp=0,end_tmp=0;
2956     long                long_tmp;
2957     struct rrd_time_value       start_tv, end_tv;
2958     gfx_color_t         color;
2959     optind = 0; opterr = 0;  /* initialize getopt */
2960
2961     parsetime("end-24h", &start_tv);
2962     parsetime("now", &end_tv);
2963
2964     while (1){
2965         static struct option long_options[] =
2966         {
2967             {"start",      required_argument, 0,  's'},
2968             {"end",        required_argument, 0,  'e'},
2969             {"x-grid",     required_argument, 0,  'x'},
2970             {"y-grid",     required_argument, 0,  'y'},
2971             {"vertical-label",required_argument,0,'v'},
2972             {"width",      required_argument, 0,  'w'},
2973             {"height",     required_argument, 0,  'h'},
2974             {"interlaced", no_argument,       0,  'i'},
2975             {"upper-limit",required_argument, 0,  'u'},
2976             {"lower-limit",required_argument, 0,  'l'},
2977             {"rigid",      no_argument,       0,  'r'},
2978             {"base",       required_argument, 0,  'b'},
2979             {"logarithmic",no_argument,       0,  'o'},
2980             {"color",      required_argument, 0,  'c'},
2981             {"font",       required_argument, 0,  'n'},
2982             {"title",      required_argument, 0,  't'},
2983             {"imginfo",    required_argument, 0,  'f'},
2984             {"imgformat",  required_argument, 0,  'a'},
2985             {"lazy",       no_argument,       0,  'z'},
2986             {"zoom",       required_argument, 0,  'm'},
2987             {"no-legend",  no_argument,       0,  'g'},
2988             {"force-rules-legend",no_argument,0,  'F'},
2989             {"only-graph", no_argument,       0,  'j'},
2990             {"alt-y-grid", no_argument,       0,  'Y'},
2991             {"no-minor",   no_argument,       0,  'I'},
2992             {"slope-mode", no_argument,       0,  'E'},
2993             {"alt-autoscale", no_argument,    0,  'A'},
2994             {"alt-autoscale-max", no_argument, 0, 'M'},
2995             {"no-gridfit", no_argument,       0,   'N'},
2996             {"units-exponent",required_argument, 0, 'X'},
2997             {"units-length",required_argument, 0, 'L'},
2998             {"step",       required_argument, 0,    'S'},
2999             {"tabwidth",   required_argument, 0,    'T'},            
3000             {"font-render-mode", required_argument, 0, 'R'},
3001             {"font-smoothing-threshold", required_argument, 0, 'B'},
3002             {"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 */
3003             {0,0,0,0}};
3004         int option_index = 0;
3005         int opt;
3006         int col_start,col_end;
3007
3008         opt = getopt_long(argc, argv, 
3009                          "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:",
3010                           long_options, &option_index);
3011
3012         if (opt == EOF)
3013             break;
3014         
3015         switch(opt) {
3016         case 'I':
3017             im->extra_flags |= NOMINOR;
3018             break;
3019         case 'Y':
3020             im->extra_flags |= ALTYGRID;
3021             break;
3022         case 'A':
3023             im->extra_flags |= ALTAUTOSCALE;
3024             break;
3025         case 'M':
3026             im->extra_flags |= ALTAUTOSCALE_MAX;
3027             break;
3028         case 'j':
3029            im->extra_flags |= ONLY_GRAPH;
3030            break;
3031         case 'g':
3032             im->extra_flags |= NOLEGEND;
3033             break;
3034         case 'F':
3035             im->extra_flags |= FORCE_RULES_LEGEND;
3036             break;
3037         case 'X':
3038             im->unitsexponent = atoi(optarg);
3039             break;
3040         case 'L':
3041             im->unitslength = atoi(optarg);
3042             break;
3043         case 'T':
3044             im->tabwidth = atof(optarg);
3045             break;
3046         case 'S':
3047             im->step =  atoi(optarg);
3048             break;
3049         case 'N':
3050             im->gridfit = 0;
3051             break;
3052         case 's':
3053             if ((parsetime_error = parsetime(optarg, &start_tv))) {
3054                 rrd_set_error( "start time: %s", parsetime_error );
3055                 return;
3056             }
3057             break;
3058         case 'e':
3059             if ((parsetime_error = parsetime(optarg, &end_tv))) {
3060                 rrd_set_error( "end time: %s", parsetime_error );
3061                 return;
3062             }
3063             break;
3064         case 'x':
3065             if(strcmp(optarg,"none") == 0){
3066               im->draw_x_grid=0;
3067               break;
3068             };
3069                 
3070             if(sscanf(optarg,
3071                       "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3072                       scan_gtm,
3073                       &im->xlab_user.gridst,
3074                       scan_mtm,
3075                       &im->xlab_user.mgridst,
3076                       scan_ltm,
3077                       &im->xlab_user.labst,
3078                       &im->xlab_user.precis,
3079                       &stroff) == 7 && stroff != 0){
3080                 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3081                 im->xlab_form[sizeof(im->xlab_form)-1] = '\0'; 
3082                 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3083                     rrd_set_error("unknown keyword %s",scan_gtm);
3084                     return;
3085                 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3086                     rrd_set_error("unknown keyword %s",scan_mtm);
3087                     return;
3088                 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3089                     rrd_set_error("unknown keyword %s",scan_ltm);
3090                     return;
3091                 } 
3092                 im->xlab_user.minsec = 1;
3093                 im->xlab_user.stst = im->xlab_form;
3094             } else {
3095                 rrd_set_error("invalid x-grid format");
3096                 return;
3097             }
3098             break;
3099         case 'y':
3100
3101             if(strcmp(optarg,"none") == 0){
3102               im->draw_y_grid=0;
3103               break;
3104             };
3105
3106             if(sscanf(optarg,
3107                       "%lf:%d",
3108                       &im->ygridstep,
3109                       &im->ylabfact) == 2) {
3110                 if(im->ygridstep<=0){
3111                     rrd_set_error("grid step must be > 0");
3112                     return;
3113                 } else if (im->ylabfact < 1){
3114                     rrd_set_error("label factor must be > 0");
3115                     return;
3116                 } 
3117             } else {
3118                 rrd_set_error("invalid y-grid format");
3119                 return;
3120             }
3121             break;
3122         case 'v':
3123             strncpy(im->ylegend,optarg,150);
3124             im->ylegend[150]='\0';
3125             break;
3126         case 'u':
3127             im->maxval = atof(optarg);
3128             break;
3129         case 'l':
3130             im->minval = atof(optarg);
3131             break;
3132         case 'b':
3133             im->base = atol(optarg);
3134             if(im->base != 1024 && im->base != 1000 ){
3135                 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3136                 return;
3137             }
3138             break;
3139         case 'w':
3140             long_tmp = atol(optarg);
3141             if (long_tmp < 10) {
3142                 rrd_set_error("width below 10 pixels");
3143                 return;
3144             }
3145             im->xsize = long_tmp;
3146             break;
3147         case 'h':
3148             long_tmp = atol(optarg);
3149             if (long_tmp < 10) {
3150                 rrd_set_error("height below 10 pixels");
3151                 return;
3152             }
3153             im->ysize = long_tmp;
3154             break;
3155         case 'i':
3156             im->canvas->interlaced = 1;
3157             break;
3158         case 'r':
3159             im->rigid = 1;
3160             break;
3161         case 'f':
3162             im->imginfo = optarg;
3163             break;
3164         case 'a':
3165             if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3166                 rrd_set_error("unsupported graphics format '%s'",optarg);
3167                 return;
3168             }
3169             break;
3170         case 'z':
3171             im->lazy = 1;
3172             break;
3173         case 'E':
3174             im->slopemode = 1;
3175             break;
3176
3177         case 'o':
3178             im->logarithmic = 1;
3179             if (isnan(im->minval))
3180                 im->minval=1;
3181             break;
3182         case 'c':
3183             if(sscanf(optarg,
3184                       "%10[A-Z]#%n%8lx%n",
3185                       col_nam,&col_start,&color,&col_end) == 2){
3186                 int ci;
3187                 int col_len = col_end - col_start;
3188                 switch (col_len){
3189                         case 3:
3190                                 color = (
3191                                         ((color & 0xF00) * 0x110000) |
3192                                         ((color & 0x0F0) * 0x011000) |
3193                                         ((color & 0x00F) * 0x001100) |
3194                                         0x000000FF
3195                                         );
3196                                 break;
3197                         case 4:
3198                                 color = (
3199                                         ((color & 0xF000) * 0x11000) |
3200                                         ((color & 0x0F00) * 0x01100) |
3201                                         ((color & 0x00F0) * 0x00110) |
3202                                         ((color & 0x000F) * 0x00011)
3203                                         );
3204                                 break;
3205                         case 6:
3206                                 color = (color << 8) + 0xff /* shift left by 8 */;
3207                                 break;
3208                         case 8:
3209                                 break;
3210                         default:
3211                                 rrd_set_error("the color format is #RRGGBB[AA]");
3212                                 return;
3213                 }
3214                 if((ci=grc_conv(col_nam)) != -1){
3215                     im->graph_col[ci]=color;
3216                 }  else {
3217                   rrd_set_error("invalid color name '%s'",col_nam);
3218                   return;
3219                 }
3220             } else {
3221                 rrd_set_error("invalid color def format");
3222                 return;
3223             }
3224             break;        
3225         case 'n':{
3226             char prop[15];
3227             double size = 1;
3228             char font[1024] = "";
3229
3230             if(sscanf(optarg,
3231                                 "%10[A-Z]:%lf:%1000s",
3232                                 prop,&size,font) >= 2){
3233                 int sindex,propidx;
3234                 if((sindex=text_prop_conv(prop)) != -1){
3235                   for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){                      
3236                       if (size > 0){
3237                           im->text_prop[propidx].size=size;              
3238                       }
3239                       if (strlen(font) > 0){
3240                           strcpy(im->text_prop[propidx].font,font);
3241                       }
3242                       if (propidx==sindex && sindex != 0) break;
3243                   }
3244                 } else {
3245                     rrd_set_error("invalid fonttag '%s'",prop);
3246                     return;
3247                 }
3248             } else {
3249                 rrd_set_error("invalid text property format");
3250                 return;
3251             }
3252             break;          
3253         }
3254         case 'm':
3255             im->canvas->zoom = atof(optarg);
3256             if (im->canvas->zoom <= 0.0) {
3257                 rrd_set_error("zoom factor must be > 0");
3258                 return;
3259             }
3260           break;
3261         case 't':
3262             strncpy(im->title,optarg,150);
3263             im->title[150]='\0';
3264             break;
3265
3266         case 'R':
3267                 if ( strcmp( optarg, "normal" ) == 0 )
3268                         im->canvas->aa_type = AA_NORMAL;
3269                 else if ( strcmp( optarg, "light" ) == 0 )
3270                         im->canvas->aa_type = AA_LIGHT;
3271                 else if ( strcmp( optarg, "mono" ) == 0 )
3272                         im->canvas->aa_type = AA_NONE;
3273                 else
3274                 {
3275                         rrd_set_error("unknown font-render-mode '%s'", optarg );
3276                         return;
3277                 }
3278                 break;
3279
3280         case 'B':
3281             im->canvas->font_aa_threshold = atof(optarg);
3282                 break;
3283
3284         case '?':
3285             if (optopt != 0)
3286                 rrd_set_error("unknown option '%c'", optopt);
3287             else
3288                 rrd_set_error("unknown option '%s'",argv[optind-1]);
3289             return;
3290         }
3291     }
3292     
3293     if (optind >= argc) {
3294        rrd_set_error("missing filename");
3295        return;
3296     }
3297
3298     if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
3299         rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");    
3300         return;
3301     }
3302
3303     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3304         /* error string is set in parsetime.c */
3305         return;
3306     }  
3307     
3308     if (start_tmp < 3600*24*365*10){
3309         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3310         return;
3311     }
3312     
3313     if (end_tmp < start_tmp) {
3314         rrd_set_error("start (%ld) should be less than end (%ld)", 
3315                start_tmp, end_tmp);
3316         return;
3317     }
3318     
3319     im->start = start_tmp;
3320     im->end = end_tmp;
3321     im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3322 }
3323
3324 int
3325 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3326 {
3327     if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3328         rrd_set_error("Unknown variable '%s' in %s",varname,err);
3329         return -1;
3330     }
3331     return 0;
3332 }
3333 int
3334 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3335 {
3336     char *color;
3337     graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3338
3339     color=strstr(var,"#");
3340     if (color==NULL) {
3341         if (optional==0) {
3342             rrd_set_error("Found no color in %s",err);
3343             return 0;
3344         }
3345         return 0;
3346     } else {
3347         int n=0;
3348         char *rest;
3349         gfx_color_t    col;
3350
3351         rest=strstr(color,":");
3352         if (rest!=NULL)
3353             n=rest-color;
3354         else
3355             n=strlen(color);
3356
3357         switch (n) {
3358             case 7:
3359                 sscanf(color,"#%6lx%n",&col,&n);
3360                 col = (col << 8) + 0xff /* shift left by 8 */;
3361                 if (n!=7) rrd_set_error("Color problem in %s",err);
3362                 break;
3363             case 9:
3364                 sscanf(color,"#%8lx%n",&col,&n);
3365                 if (n==9) break;
3366             default:
3367                 rrd_set_error("Color problem in %s",err);
3368         }
3369         if (rrd_test_error()) return 0;
3370         gdp->col = col;
3371         return n;
3372     }
3373 }
3374
3375
3376 int bad_format(char *fmt) {
3377     char *ptr;
3378     int n=0;
3379     ptr = fmt;
3380     while (*ptr != '\0')
3381         if (*ptr++ == '%') {
3382  
3383              /* line cannot end with percent char */
3384              if (*ptr == '\0') return 1;
3385  
3386              /* '%s', '%S' and '%%' are allowed */
3387              if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3388
3389              /* %c is allowed (but use only with vdef!) */
3390              else if (*ptr == 'c') {
3391                 ptr++;
3392                 n=1;
3393              }
3394
3395              /* or else '% 6.2lf' and such are allowed */
3396              else {
3397                  /* optional padding character */
3398                  if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3399
3400                  /* This should take care of 'm.n' with all three optional */
3401                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3402                  if (*ptr == '.') ptr++;
3403                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3404   
3405                  /* Either 'le', 'lf' or 'lg' must follow here */
3406                  if (*ptr++ != 'l') return 1;
3407                  if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3408                  else return 1;
3409                  n++;
3410             }
3411          }
3412       
3413       return (n!=1); 
3414 }
3415
3416
3417 int
3418 vdef_parse(gdes,str)
3419 struct graph_desc_t *gdes;
3420 const char *const str;
3421 {
3422     /* A VDEF currently is either "func" or "param,func"
3423      * so the parsing is rather simple.  Change if needed.
3424      */
3425     double      param;
3426     char        func[30];
3427     int         n;
3428     
3429     n=0;
3430     sscanf(str,"%le,%29[A-Z]%n",&param,func,&n);
3431     if (n== (int)strlen(str)) { /* matched */
3432         ;
3433     } else {
3434         n=0;
3435         sscanf(str,"%29[A-Z]%n",func,&n);
3436         if (n== (int)strlen(str)) { /* matched */
3437             param=DNAN;
3438         } else {
3439             rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3440                 ,str
3441                 ,gdes->vname
3442                 );
3443             return -1;
3444         }
3445     }
3446     if          (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3447     else if     (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3448     else if     (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3449     else if     (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3450     else if     (!strcmp("TOTAL",  func)) gdes->vf.op = VDEF_TOTAL;
3451     else if     (!strcmp("FIRST",  func)) gdes->vf.op = VDEF_FIRST;
3452     else if     (!strcmp("LAST",   func)) gdes->vf.op = VDEF_LAST;
3453     else if     (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3454     else if     (!strcmp("LSLINT",   func)) gdes->vf.op = VDEF_LSLINT;
3455     else if     (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3456     else {
3457         rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3458             ,func
3459             ,gdes->vname
3460             );
3461         return -1;
3462     };
3463
3464     switch (gdes->vf.op) {
3465         case VDEF_PERCENT:
3466             if (isnan(param)) { /* no parameter given */
3467                 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3468                     ,func
3469                     ,gdes->vname
3470                     );
3471                 return -1;
3472             };
3473             if (param>=0.0 && param<=100.0) {
3474                 gdes->vf.param = param;
3475                 gdes->vf.val   = DNAN;  /* undefined */
3476                 gdes->vf.when  = 0;     /* undefined */
3477             } else {
3478                 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3479                     ,param
3480                     ,gdes->vname
3481                     );
3482                 return -1;
3483             };
3484             break;
3485         case VDEF_MAXIMUM:
3486         case VDEF_AVERAGE:
3487         case VDEF_MINIMUM:
3488         case VDEF_TOTAL:
3489         case VDEF_FIRST:
3490         case VDEF_LAST:
3491         case VDEF_LSLSLOPE:
3492         case VDEF_LSLINT:
3493         case VDEF_LSLCORREL:
3494             if (isnan(param)) {
3495                 gdes->vf.param = DNAN;
3496                 gdes->vf.val   = DNAN;
3497                 gdes->vf.when  = 0;
3498             } else {
3499                 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3500                     ,func
3501                     ,gdes->vname
3502                     );
3503                 return -1;
3504             };
3505             break;
3506     };
3507     return 0;
3508 }
3509
3510
3511 int
3512 vdef_calc(im,gdi)
3513 image_desc_t *im;
3514 int gdi;
3515 {
3516     graph_desc_t        *src,*dst;
3517     rrd_value_t         *data;
3518     long                step,steps;
3519
3520     dst = &im->gdes[gdi];
3521     src = &im->gdes[dst->vidx];
3522     data = src->data + src->ds;
3523     steps = (src->end - src->start) / src->step;
3524
3525 #if 0
3526 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3527     ,src->start
3528     ,src->end
3529     ,steps
3530     );
3531 #endif
3532
3533     switch (dst->vf.op) {
3534         case VDEF_PERCENT: {
3535                 rrd_value_t *   array;
3536                 int             field;
3537
3538
3539                 if ((array = malloc(steps*sizeof(double)))==NULL) {
3540                     rrd_set_error("malloc VDEV_PERCENT");
3541                     return -1;
3542                 }
3543                 for (step=0;step < steps; step++) {
3544                     array[step]=data[step*src->ds_cnt];
3545                 }
3546                 qsort(array,step,sizeof(double),vdef_percent_compar);
3547
3548                 field = (steps-1)*dst->vf.param/100;
3549                 dst->vf.val  = array[field];
3550                 dst->vf.when = 0;       /* no time component */
3551                 free(array);
3552 #if 0
3553 for(step=0;step<steps;step++)
3554 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3555 #endif
3556             }
3557             break;
3558         case VDEF_MAXIMUM:
3559             step=0;
3560             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3561             if (step == steps) {
3562                 dst->vf.val  = DNAN;
3563                 dst->vf.when = 0;
3564             } else {
3565                 dst->vf.val  = data[step*src->ds_cnt];
3566                 dst->vf.when = src->start + (step+1)*src->step;
3567             }
3568             while (step != steps) {
3569                 if (finite(data[step*src->ds_cnt])) {
3570                     if (data[step*src->ds_cnt] > dst->vf.val) {
3571                         dst->vf.val  = data[step*src->ds_cnt];
3572                         dst->vf.when = src->start + (step+1)*src->step;
3573                     }
3574                 }
3575                 step++;
3576             }
3577             break;
3578         case VDEF_TOTAL:
3579         case VDEF_AVERAGE: {
3580             int cnt=0;
3581             double sum=0.0;
3582             for (step=0;step<steps;step++) {
3583                 if (finite(data[step*src->ds_cnt])) {
3584                     sum += data[step*src->ds_cnt];
3585                     cnt ++;
3586                 };
3587             }
3588             if (cnt) {
3589                 if (dst->vf.op == VDEF_TOTAL) {
3590                     dst->vf.val  = sum*src->step;
3591                     dst->vf.when = cnt*src->step;       /* not really "when" */
3592                 } else {
3593                     dst->vf.val = sum/cnt;
3594                     dst->vf.when = 0;   /* no time component */
3595                 };
3596             } else {
3597                 dst->vf.val  = DNAN;
3598                 dst->vf.when = 0;
3599             }
3600             }
3601             break;
3602         case VDEF_MINIMUM:
3603             step=0;
3604             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3605             if (step == steps) {
3606                 dst->vf.val  = DNAN;
3607                 dst->vf.when = 0;
3608             } else {
3609                 dst->vf.val  = data[step*src->ds_cnt];
3610                 dst->vf.when = src->start + (step+1)*src->step;
3611             }
3612             while (step != steps) {
3613                 if (finite(data[step*src->ds_cnt])) {
3614                     if (data[step*src->ds_cnt] < dst->vf.val) {
3615                         dst->vf.val  = data[step*src->ds_cnt];
3616                         dst->vf.when = src->start + (step+1)*src->step;
3617                     }
3618                 }
3619                 step++;
3620             }
3621             break;
3622         case VDEF_FIRST:
3623             /* The time value returned here is one step before the
3624              * actual time value.  This is the start of the first
3625              * non-NaN interval.
3626              */
3627             step=0;
3628             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3629             if (step == steps) { /* all entries were NaN */
3630                 dst->vf.val  = DNAN;
3631                 dst->vf.when = 0;
3632             } else {
3633                 dst->vf.val  = data[step*src->ds_cnt];
3634                 dst->vf.when = src->start + step*src->step;
3635             }
3636             break;
3637         case VDEF_LAST:
3638             /* The time value returned here is the
3639              * actual time value.  This is the end of the last
3640              * non-NaN interval.
3641              */
3642             step=steps-1;
3643             while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3644             if (step < 0) { /* all entries were NaN */
3645                 dst->vf.val  = DNAN;
3646                 dst->vf.when = 0;
3647             } else {
3648                 dst->vf.val  = data[step*src->ds_cnt];
3649                 dst->vf.when = src->start + (step+1)*src->step;
3650             }
3651             break;
3652         case VDEF_LSLSLOPE:
3653         case VDEF_LSLINT:
3654         case VDEF_LSLCORREL:{
3655             /* Bestfit line by linear least squares method */ 
3656
3657             int cnt=0;
3658             double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3659             SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3660
3661             for (step=0;step<steps;step++) {
3662                 if (finite(data[step*src->ds_cnt])) {
3663                     cnt++;
3664                     SUMx  += step;
3665                     SUMxx += step * step;
3666                     SUMxy += step * data[step*src->ds_cnt];
3667                     SUMy  += data[step*src->ds_cnt];
3668                     SUMyy  += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3669                 };
3670             }
3671
3672             slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3673             y_intercept = ( SUMy - slope*SUMx ) / cnt;
3674             correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3675
3676             if (cnt) {
3677                     if (dst->vf.op == VDEF_LSLSLOPE) {
3678                         dst->vf.val  = slope;
3679                         dst->vf.when = cnt*src->step;
3680                     } else if (dst->vf.op == VDEF_LSLINT)  {
3681                         dst->vf.val = y_intercept;
3682                         dst->vf.when = cnt*src->step;
3683                     } else if (dst->vf.op == VDEF_LSLCORREL)  {
3684                         dst->vf.val = correl;
3685                         dst->vf.when = cnt*src->step;
3686                     };
3687                 
3688             } else {
3689                 dst->vf.val  = DNAN;
3690                 dst->vf.when = 0;
3691             }
3692             }
3693             break;
3694     }
3695     return 0;
3696 }
3697
3698 /* NaN < -INF < finite_values < INF */
3699 int
3700 vdef_percent_compar(a,b)
3701 const void *a,*b;
3702 {
3703     /* Equality is not returned; this doesn't hurt except
3704      * (maybe) for a little performance.
3705      */
3706
3707     /* First catch NaN values. They are smallest */
3708     if (isnan( *(double *)a )) return -1;
3709     if (isnan( *(double *)b )) return  1;
3710
3711     /* NaN doesn't reach this part so INF and -INF are extremes.
3712      * The sign from isinf() is compatible with the sign we return
3713      */
3714     if (isinf( *(double *)a )) return isinf( *(double *)a );
3715     if (isinf( *(double *)b )) return isinf( *(double *)b );
3716
3717     /* If we reach this, both values must be finite */
3718     if ( *(double *)a < *(double *)b ) return -1; else return 1;
3719 }