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