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