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