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