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