small fixes -- Alex van den Bogaerdt <alex@slot.hollandcasino.nl>
[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             vidx = im->gdes[i].vidx;
1307             max_ii =((im->gdes[vidx].end 
1308                       - im->gdes[vidx].start)
1309                      /im->gdes[vidx].step
1310                      *im->gdes[vidx].ds_cnt);
1311             printval = DNAN;
1312             validsteps = 0;
1313             for(ii=im->gdes[vidx].ds+im->gdes[vidx].ds_cnt;
1314                 ii < max_ii+im->gdes[vidx].ds_cnt;
1315                 ii+=im->gdes[vidx].ds_cnt){
1316                  if (! finite(im->gdes[vidx].data[ii]))
1317                      continue;
1318                  if (isnan(printval)){
1319                      printval = im->gdes[vidx].data[ii];
1320                      validsteps++;
1321                     continue;
1322                 }
1323
1324                 switch (im->gdes[i].cf){
1325                 case CF_HWPREDICT:
1326                 case CF_DEVPREDICT:
1327                 case CF_DEVSEASONAL:
1328                 case CF_SEASONAL:
1329                 case CF_AVERAGE:
1330                     validsteps++;
1331                     printval += im->gdes[vidx].data[ii];
1332                     break;
1333                 case CF_MINIMUM:
1334                     printval = min( printval, im->gdes[vidx].data[ii]);
1335                     break;
1336             case CF_FAILURES:
1337                 case CF_MAXIMUM:
1338                     printval = max( printval, im->gdes[vidx].data[ii]);
1339                     break;
1340                 case CF_LAST:
1341                     printval = im->gdes[vidx].data[ii];
1342                 }
1343             }
1344             if (im->gdes[i].cf ==  CF_AVERAGE || im -> gdes[i].cf > CF_LAST) {
1345                 if (validsteps > 1) {
1346                     printval = (printval / validsteps);
1347                 }
1348             }
1349             if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1350                 /* Magfact is set to -1 upon entry to print_calc.  If it
1351                  * is still less than 0, then we need to run auto_scale.
1352                  * Otherwise, put the value into the correct units.  If
1353                  * the value is 0, then do not set the symbol or magnification
1354                  * so next the calculation will be performed again. */
1355                 if (magfact < 0.0) {
1356                     auto_scale(im,&printval,&si_symb,&magfact);
1357                     if (printval == 0.0)
1358                         magfact = -1.0;
1359                 } else {
1360                     printval /= magfact;
1361                 }
1362                 *(++percent_s) = 's';
1363             }
1364             else if (strstr(im->gdes[i].format,"%s") != NULL) {
1365                 auto_scale(im,&printval,&si_symb,&magfact);
1366             }
1367             if (im->gdes[i].gf == GF_PRINT){
1368                 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1369                 if (bad_format(im->gdes[i].format)) {
1370                         rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1371                         return -1;
1372                 }
1373 #ifdef HAVE_SNPRINTF
1374                 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1375 #else
1376                 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1377 #endif
1378                 (*prdata)[prlines-1] = NULL;
1379             } else {
1380                 /* GF_GPRINT */
1381
1382                 if (bad_format(im->gdes[i].format)) {
1383                         rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1384                         return -1;
1385                 }
1386 #ifdef HAVE_SNPRINTF
1387                 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1388 #else
1389                 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1390 #endif
1391                 graphelement = 1;
1392             }
1393             break;
1394         case GF_COMMENT:
1395         case GF_LINE1:
1396         case GF_LINE2:
1397         case GF_LINE3:
1398         case GF_AREA:
1399         case GF_TICK:
1400         case GF_STACK:
1401         case GF_HRULE:
1402         case GF_VRULE:
1403             graphelement = 1;
1404             break;
1405         case GF_DEF:
1406         case GF_CDEF:       
1407         case GF_VDEF:       
1408             break;
1409         }
1410     }
1411     return graphelement;
1412 }
1413
1414
1415 /* place legends with color spots */
1416 int
1417 leg_place(image_desc_t *im)
1418 {
1419     /* graph labels */
1420     int   interleg = SmallFont->w*2;
1421     int   box = SmallFont->h*1.2;
1422     int   border = SmallFont->w*2;
1423     int   fill=0, fill_last;
1424     int   leg_c = 0;
1425     int   leg_x = border, leg_y = im->ygif;
1426     int   leg_cc;
1427     int   glue = 0;
1428     int   i,ii, mark = 0;
1429     char  prt_fctn; /*special printfunctions */
1430     int  *legspace;
1431
1432   if( !(im->extra_flags & NOLEGEND) ) {
1433     if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1434        rrd_set_error("malloc for legspace");
1435        return -1;
1436     }
1437
1438     for(i=0;i<im->gdes_c;i++){
1439         fill_last = fill;
1440
1441         leg_cc = strlen(im->gdes[i].legend);
1442         
1443         /* is there a controle code ant the end of the legend string ? */ 
1444         if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1445             prt_fctn = im->gdes[i].legend[leg_cc-1];
1446             leg_cc -= 2;
1447             im->gdes[i].legend[leg_cc] = '\0';
1448         } else {
1449             prt_fctn = '\0';
1450         }
1451         /* remove exess space */
1452         while (prt_fctn=='g' && 
1453                leg_cc > 0 && 
1454                im->gdes[i].legend[leg_cc-1]==' '){
1455            leg_cc--;
1456            im->gdes[i].legend[leg_cc]='\0';
1457         }
1458         if (leg_cc != 0 ){          
1459            legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1460            
1461            if (fill > 0){ 
1462                /* no interleg space if string ends in \g */
1463                fill += legspace[i];
1464             }
1465             if (im->gdes[i].gf != GF_GPRINT && 
1466                 im->gdes[i].gf != GF_COMMENT) { 
1467                 fill += box;       
1468             }
1469             fill += leg_cc * SmallFont->w;
1470             leg_c++;
1471         } else {
1472            legspace[i]=0;
1473         }
1474         /* who said there was a special tag ... ?*/
1475         if (prt_fctn=='g') {    
1476            prt_fctn = '\0';
1477         }
1478         if (prt_fctn == '\0') {
1479             if (i == im->gdes_c -1 ) prt_fctn ='l';
1480             
1481             /* is it time to place the legends ? */
1482             if (fill > im->xgif - 2*border){
1483                 if (leg_c > 1) {
1484                     /* go back one */
1485                     i--; 
1486                     fill = fill_last;
1487                     leg_c--;
1488                     prt_fctn = 'j';
1489                 } else {
1490                     prt_fctn = 'l';
1491                 }
1492                 
1493             }
1494         }
1495
1496
1497         if (prt_fctn != '\0'){
1498             leg_x = border;
1499             if (leg_c >= 2 && prt_fctn == 'j') {
1500                 glue = (im->xgif - fill - 2* border) / (leg_c-1);
1501                 /* if (glue > 2 * SmallFont->w) glue = 0; */
1502             } else {
1503                 glue = 0;
1504             }
1505             if (prt_fctn =='c') leg_x =  (im->xgif - fill) / 2.0;
1506             if (prt_fctn =='r') leg_x =  im->xgif - fill - border;
1507
1508             for(ii=mark;ii<=i;ii++){
1509                 if(im->gdes[ii].legend[0]=='\0')
1510                     continue;
1511                 im->gdes[ii].legloc.x = leg_x;
1512                 im->gdes[ii].legloc.y = leg_y;
1513                 leg_x =  leg_x 
1514                     + strlen(im->gdes[ii].legend)*SmallFont->w 
1515                     + legspace[ii]
1516                     + glue;
1517                 if (im->gdes[ii].gf != GF_GPRINT && 
1518                     im->gdes[ii].gf != GF_COMMENT) 
1519                     leg_x += box;          
1520             }       
1521             leg_y = leg_y + SmallFont->h*1.2;
1522             if (prt_fctn == 's') leg_y -= SmallFont->h *0.5;
1523             fill = 0;
1524             leg_c = 0;
1525             mark = ii;
1526         }          
1527     }
1528     im->ygif = leg_y+6;
1529     free(legspace);
1530   }
1531   return 0;
1532 }
1533
1534 /* create a grid on the graph. it determines what to do
1535    from the values of xsize, start and end */
1536
1537 /* the xaxis labels are determined from the number of seconds per pixel
1538    in the requested graph */
1539
1540
1541
1542 int
1543 horizontal_grid(gdImagePtr gif, image_desc_t   *im)
1544 {
1545     double   range;
1546     double   scaledrange;
1547     int      pixel,i;
1548     int      sgrid,egrid;
1549     double   gridstep;
1550     double   scaledstep;
1551     char     graph_label[100];
1552     gdPoint  polyPoints[4];
1553     int      labfact,gridind;
1554     int      styleMinor[2],styleMajor[2];
1555     int      decimals, fractionals;
1556     char     labfmt[64];
1557
1558     labfact=2;
1559     gridind=-1;
1560     range =  im->maxval - im->minval;
1561     scaledrange = range / im->magfact;
1562
1563         /* does the scale of this graph make it impossible to put lines
1564            on it? If so, give up. */
1565         if (isnan(scaledrange)) {
1566                 return 0;
1567         }
1568
1569     styleMinor[0] = graph_col[GRC_GRID].i;
1570     styleMinor[1] = gdTransparent;
1571
1572     styleMajor[0] = graph_col[GRC_MGRID].i;
1573     styleMajor[1] = gdTransparent;
1574
1575     /* find grid spaceing */
1576     pixel=1;
1577     if(isnan(im->ygridstep)){
1578         if(im->extra_flags & ALTYGRID) {
1579             /* find the value with max number of digits. Get number of digits */
1580             decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1581             if(decimals <= 0) /* everything is small. make place for zero */
1582                 decimals = 1;
1583             
1584             fractionals = floor(log10(range));
1585             if(fractionals < 0) /* small amplitude. */
1586                 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1587             else
1588                 sprintf(labfmt, "%%%d.1f", decimals + 1);
1589             gridstep = pow((double)10, (double)fractionals);
1590             if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1591                 gridstep = 0.1;
1592             /* should have at least 5 lines but no more then 15 */
1593             if(range/gridstep < 5)
1594                 gridstep /= 10;
1595             if(range/gridstep > 15)
1596                 gridstep *= 10;
1597             if(range/gridstep > 5) {
1598                 labfact = 1;
1599                 if(range/gridstep > 8)
1600                     labfact = 2;
1601             }
1602             else {
1603                 gridstep /= 5;
1604                 labfact = 5;
1605             }
1606         }
1607         else {
1608             for(i=0;ylab[i].grid > 0;i++){
1609                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1610                 if (gridind == -1 && pixel > 5) {
1611                     gridind = i;
1612                     break;
1613                 }
1614             }
1615             
1616             for(i=0; i<4;i++) {
1617                 if (pixel * ylab[gridind].lfac[i] >=  2 * SmallFont->h) {
1618                     labfact =  ylab[gridind].lfac[i];
1619                     break;
1620                 }                         
1621             } 
1622             
1623             gridstep = ylab[gridind].grid * im->magfact;
1624         }
1625     } else {
1626         gridstep = im->ygridstep;
1627         labfact = im->ylabfact;
1628     }
1629     
1630     polyPoints[0].x=im->xorigin;
1631     polyPoints[1].x=im->xorigin+im->xsize;
1632     sgrid = (int)( im->minval / gridstep - 1);
1633     egrid = (int)( im->maxval / gridstep + 1);
1634     scaledstep = gridstep/im->magfact;
1635     for (i = sgrid; i <= egrid; i++){
1636         polyPoints[0].y=ytr(im,gridstep*i);
1637         if ( polyPoints[0].y >= im->yorigin-im->ysize
1638              && polyPoints[0].y <= im->yorigin) {
1639             if(i % labfact == 0){               
1640                 if (i==0 || im->symbol == ' ') {
1641                     if(scaledstep < 1){
1642                         if(im->extra_flags & ALTYGRID) {
1643                             sprintf(graph_label,labfmt,scaledstep*i);
1644                         }
1645                         else {
1646                             sprintf(graph_label,"%4.1f",scaledstep*i);
1647                         }
1648                     } else {
1649                         sprintf(graph_label,"%4.0f",scaledstep*i);
1650                     }
1651                 }else {
1652                     if(scaledstep < 1){
1653                         sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1654                     } else {
1655                         sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1656                     }
1657                 }
1658
1659                 gdImageString(gif, SmallFont,
1660                               (polyPoints[0].x - (strlen(graph_label) * 
1661                                                   SmallFont->w)-7), 
1662                               polyPoints[0].y - SmallFont->h/2+1,
1663                               (unsigned char *)graph_label, graph_col[GRC_FONT].i);
1664                 
1665                 gdImageSetStyle(gif, styleMajor, 2);
1666
1667                 gdImageLine(gif, polyPoints[0].x-2,polyPoints[0].y,
1668                             polyPoints[0].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
1669                 gdImageLine(gif, polyPoints[1].x-2,polyPoints[0].y,
1670                             polyPoints[1].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);              
1671             } else {            
1672                 gdImageSetStyle(gif, styleMinor, 2);
1673                 gdImageLine(gif, polyPoints[0].x-1,polyPoints[0].y,
1674                             polyPoints[0].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
1675                 gdImageLine(gif, polyPoints[1].x-1,polyPoints[0].y,
1676                             polyPoints[1].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);               
1677             }       
1678             gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1679                         polyPoints[1].x,polyPoints[0].y,gdStyled);
1680         }       
1681     } 
1682 /*    if(im->minval * im->maxval < 0){
1683       polyPoints[0].y=ytr(0);
1684       gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1685       polyPoints[1].x,polyPoints[0].y,graph_col[GRC_MGRID].i);
1686       } */
1687
1688     return 1;
1689 }
1690
1691 /* logaritmic horizontal grid */
1692 int
1693 horizontal_log_grid(gdImagePtr gif, image_desc_t   *im)
1694 {
1695     double   pixpex;
1696     int      ii,i;
1697     int      minoridx=0, majoridx=0;
1698     char     graph_label[100];
1699     gdPoint  polyPoints[4];
1700     int      styleMinor[2],styleMajor[2];
1701     double   value, pixperstep, minstep;
1702
1703     /* find grid spaceing */
1704     pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1705
1706         if (isnan(pixpex)) {
1707                 return 0;
1708         }
1709
1710     for(i=0;yloglab[i][0] > 0;i++){
1711         minstep = log10(yloglab[i][0]);
1712         for(ii=1;yloglab[i][ii+1] > 0;ii++){
1713             if(yloglab[i][ii+2]==0){
1714                 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1715                 break;
1716             }
1717         }
1718         pixperstep = pixpex * minstep;
1719         if(pixperstep > 5){minoridx = i;}
1720         if(pixperstep > 2 * SmallFont->h){majoridx = i;}
1721     }
1722    
1723     styleMinor[0] = graph_col[GRC_GRID].i;
1724     styleMinor[1] = gdTransparent;
1725
1726     styleMajor[0] = graph_col[GRC_MGRID].i;
1727     styleMajor[1] = gdTransparent;
1728
1729     polyPoints[0].x=im->xorigin;
1730     polyPoints[1].x=im->xorigin+im->xsize;
1731     /* paint minor grid */
1732     for (value = pow((double)10, log10(im->minval) 
1733                           - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1734          value  <= im->maxval;
1735          value *= yloglab[minoridx][0]){
1736         if (value < im->minval) continue;
1737         i=0;    
1738         while(yloglab[minoridx][++i] > 0){          
1739             polyPoints[0].y = ytr(im,value * yloglab[minoridx][i]);
1740             if (polyPoints[0].y <= im->yorigin - im->ysize) break;
1741             gdImageSetStyle(gif, styleMinor, 2);
1742             gdImageLine(gif, polyPoints[0].x-1,polyPoints[0].y,
1743                         polyPoints[0].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);
1744             gdImageLine(gif, polyPoints[1].x-1,polyPoints[0].y,
1745                         polyPoints[1].x+1,polyPoints[0].y,graph_col[GRC_GRID].i);           
1746
1747             gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1748                         polyPoints[1].x,polyPoints[0].y,gdStyled);
1749         }
1750     }
1751
1752     /* paint major grid and labels*/
1753     for (value = pow((double)10, log10(im->minval) 
1754                           - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1755          value <= im->maxval;
1756          value *= yloglab[majoridx][0]){
1757         if (value < im->minval) continue;
1758         i=0;    
1759         while(yloglab[majoridx][++i] > 0){          
1760             polyPoints[0].y = ytr(im,value * yloglab[majoridx][i]);         
1761             if (polyPoints[0].y <= im->yorigin - im->ysize) break;
1762             gdImageSetStyle(gif, styleMajor, 2);
1763             gdImageLine(gif, polyPoints[0].x-2,polyPoints[0].y,
1764                         polyPoints[0].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);
1765             gdImageLine(gif, polyPoints[1].x-2,polyPoints[0].y,
1766                         polyPoints[1].x+2,polyPoints[0].y,graph_col[GRC_MGRID].i);                  
1767             
1768             gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1769                         polyPoints[1].x,polyPoints[0].y,gdStyled);
1770             sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1771             gdImageString(gif, SmallFont,
1772                           (polyPoints[0].x - (strlen(graph_label) * 
1773                                               SmallFont->w)-7), 
1774                           polyPoints[0].y - SmallFont->h/2+1,
1775                           (unsigned char *)graph_label, graph_col[GRC_FONT].i); 
1776         } 
1777     }
1778         return 1;
1779 }
1780
1781
1782 void
1783 vertical_grid(
1784     gdImagePtr     gif,
1785     image_desc_t   *im )
1786 {   
1787     int xlab_sel;               /* which sort of label and grid ? */
1788     time_t ti, tilab;
1789     long factor;
1790     char graph_label[100];
1791     gdPoint polyPoints[4];       /* points for filled graph and more*/
1792
1793     /* style for grid lines */
1794     int     styleDotted[4];
1795
1796     
1797     /* the type of time grid is determined by finding
1798        the number of seconds per pixel in the graph */
1799     
1800     
1801     if(im->xlab_user.minsec == -1){
1802         factor=(im->end - im->start)/im->xsize;
1803         xlab_sel=0;
1804         while ( xlab[xlab_sel+1].minsec != -1 
1805                 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1806         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1807         im->xlab_user.gridst = xlab[xlab_sel].gridst;
1808         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1809         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1810         im->xlab_user.labtm = xlab[xlab_sel].labtm;
1811         im->xlab_user.labst = xlab[xlab_sel].labst;
1812         im->xlab_user.precis = xlab[xlab_sel].precis;
1813         im->xlab_user.stst = xlab[xlab_sel].stst;
1814     }
1815     
1816     /* y coords are the same for every line ... */
1817     polyPoints[0].y = im->yorigin;
1818     polyPoints[1].y = im->yorigin-im->ysize;
1819
1820     /* paint the minor grid */
1821     for(ti = find_first_time(im->start,
1822                             im->xlab_user.gridtm,
1823                             im->xlab_user.gridst);
1824         ti < im->end; 
1825         ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1826         ){
1827         /* are we inside the graph ? */
1828         if (ti < im->start || ti > im->end) continue;
1829         polyPoints[0].x = xtr(im,ti);
1830         styleDotted[0] = graph_col[GRC_GRID].i;
1831         styleDotted[1] = gdTransparent;
1832
1833         gdImageSetStyle(gif, styleDotted, 2);
1834
1835         gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1836                     polyPoints[0].x,polyPoints[1].y,gdStyled);
1837         gdImageLine(gif, polyPoints[0].x,polyPoints[0].y-1,
1838                     polyPoints[0].x,polyPoints[0].y+1,graph_col[GRC_GRID].i);
1839         gdImageLine(gif, polyPoints[0].x,polyPoints[1].y-1,
1840                     polyPoints[0].x,polyPoints[1].y+1,graph_col[GRC_GRID].i);
1841     }
1842
1843     /* paint the major grid */
1844     for(ti = find_first_time(im->start,
1845                             im->xlab_user.mgridtm,
1846                             im->xlab_user.mgridst);
1847         ti < im->end; 
1848         ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1849         ){
1850         /* are we inside the graph ? */
1851         if (ti < im->start || ti > im->end) continue;
1852         polyPoints[0].x = xtr(im,ti);
1853         styleDotted[0] = graph_col[GRC_MGRID].i;
1854         styleDotted[1] = gdTransparent;
1855         gdImageSetStyle(gif, styleDotted, 2);
1856
1857         gdImageLine(gif, polyPoints[0].x,polyPoints[0].y,
1858                     polyPoints[0].x,polyPoints[1].y,gdStyled);
1859         gdImageLine(gif, polyPoints[0].x,polyPoints[0].y-2,
1860                     polyPoints[0].x,polyPoints[0].y+2,graph_col[GRC_MGRID].i);
1861         gdImageLine(gif, polyPoints[0].x,polyPoints[1].y-2,
1862                     polyPoints[0].x,polyPoints[1].y+2,graph_col[GRC_MGRID].i);
1863     }
1864     /* paint the labels below the graph */
1865     for(ti = find_first_time(im->start,
1866                             im->xlab_user.labtm,
1867                             im->xlab_user.labst);
1868         ti <= im->end; 
1869         ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1870         ){
1871         int gr_pos,width;
1872         tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1873
1874 #if HAVE_STRFTIME
1875         strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1876 #else
1877 # error "your libc has no strftime I guess we'll abort the exercise here."
1878 #endif
1879         width=strlen(graph_label) *  SmallFont->w;
1880         gr_pos=xtr(im,tilab) - width/2;
1881         if (gr_pos  >= im->xorigin 
1882             && gr_pos + width <= im->xorigin+im->xsize) 
1883             gdImageString(gif, SmallFont,
1884                           gr_pos,  polyPoints[0].y+4,
1885                           (unsigned char *)graph_label, graph_col[GRC_FONT].i);
1886     }
1887
1888 }
1889
1890
1891 void 
1892 axis_paint(
1893     image_desc_t   *im,
1894     gdImagePtr     gif
1895     )
1896 {   
1897     /* draw x and y axis */
1898     gdImageLine(gif, im->xorigin+im->xsize,im->yorigin,
1899                 im->xorigin+im->xsize,im->yorigin-im->ysize,
1900                 graph_col[GRC_GRID].i);
1901     
1902     gdImageLine(gif, im->xorigin,im->yorigin-im->ysize,
1903                 im->xorigin+im->xsize,im->yorigin-im->ysize,
1904                 graph_col[GRC_GRID].i);
1905
1906     gdImageLine(gif, im->xorigin-4,im->yorigin,
1907                 im->xorigin+im->xsize+4,im->yorigin,
1908                 graph_col[GRC_FONT].i);
1909
1910     gdImageLine(gif, im->xorigin,im->yorigin,
1911                 im->xorigin,im->yorigin-im->ysize,
1912                 graph_col[GRC_GRID].i);
1913     
1914     /* arrow for X axis direction */
1915     gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-3, im->xorigin+im->xsize+4, im->yorigin+3,graph_col[GRC_ARROW].i);
1916     gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-3, im->xorigin+im->xsize+9, im->yorigin,graph_col[GRC_ARROW].i);
1917     gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin+3, im->xorigin+im->xsize+9, im->yorigin,graph_col[GRC_ARROW].i);
1918
1919     /*    gdImageLine(gif, im->xorigin+im->xsize-1, im->yorigin-3, im->xorigin+im->xsize-1, im->yorigin+3,graph_col[GRC_MGRID].i);
1920     gdImageLine(gif, im->xorigin+im->xsize, im->yorigin-2, im->xorigin+im->xsize, im->yorigin+2,graph_col[GRC_MGRID].i);
1921     gdImageLine(gif, im->xorigin+im->xsize+1, im->yorigin-2, im->xorigin+im->xsize+1, im->yorigin+2,graph_col[GRC_MGRID].i);
1922     gdImageLine(gif, im->xorigin+im->xsize+2, im->yorigin-2, im->xorigin+im->xsize+2, im->yorigin+2,graph_col[GRC_MGRID].i);
1923     gdImageLine(gif, im->xorigin+im->xsize+3, im->yorigin-1, im->xorigin+im->xsize+3, im->yorigin+1,graph_col[GRC_MGRID].i);
1924     gdImageLine(gif, im->xorigin+im->xsize+4, im->yorigin-1, im->xorigin+im->xsize+4, im->yorigin+1,graph_col[GRC_MGRID].i);
1925     gdImageLine(gif, im->xorigin+im->xsize+5, im->yorigin, im->xorigin+im->xsize+5, im->yorigin,graph_col[GRC_MGRID].i); */
1926
1927
1928
1929 }
1930
1931 void
1932 grid_paint(
1933     image_desc_t   *im,
1934     gdImagePtr     gif
1935     )
1936 {   
1937     long i;
1938     int boxH=8, boxV=8;
1939     int res=0;
1940     gdPoint polyPoints[4];       /* points for filled graph and more*/
1941
1942     /* draw 3d border */
1943     gdImageLine(gif,0,0,im->xgif-1,0,graph_col[GRC_SHADEA].i);
1944     gdImageLine(gif,1,1,im->xgif-2,1,graph_col[GRC_SHADEA].i);
1945     gdImageLine(gif,0,0,0,im->ygif-1,graph_col[GRC_SHADEA].i);
1946     gdImageLine(gif,1,1,1,im->ygif-2,graph_col[GRC_SHADEA].i);
1947     gdImageLine(gif,im->xgif-1,0,im->xgif-1,im->ygif-1,graph_col[GRC_SHADEB].i);
1948     gdImageLine(gif,0,im->ygif-1,im->xgif-1,im->ygif-1,graph_col[GRC_SHADEB].i);
1949     gdImageLine(gif,im->xgif-2,1,im->xgif-2,im->ygif-2,graph_col[GRC_SHADEB].i);
1950     gdImageLine(gif,1,im->ygif-2,im->xgif-2,im->ygif-2,graph_col[GRC_SHADEB].i);
1951
1952
1953     if (im->draw_x_grid == 1 )
1954       vertical_grid(gif, im);
1955     
1956     if (im->draw_y_grid == 1){
1957         if(im->logarithmic){
1958                 res = horizontal_log_grid(gif,im);
1959         } else {
1960                 res = horizontal_grid(gif,im);
1961         }
1962
1963         /* dont draw horizontal grid if there is no min and max val */
1964         if (! res ) {
1965           char *nodata = "No Data found";
1966           gdImageString(gif, LargeFont,
1967                         im->xgif/2 
1968                         - (strlen(nodata)*LargeFont->w)/2,
1969                         (2*im->yorigin-im->ysize) / 2,
1970                         (unsigned char *)nodata, graph_col[GRC_FONT].i);
1971         }
1972     }
1973
1974     /* yaxis description */
1975     gdImageStringUp(gif, SmallFont,
1976                     7,
1977                     (im->yorigin - im->ysize/2
1978                      +(strlen(im->ylegend)*SmallFont->w)/2 ),
1979                     (unsigned char *)im->ylegend, graph_col[GRC_FONT].i);
1980     
1981
1982     /* graph title */
1983     gdImageString(gif, LargeFont,
1984                     im->xgif/2 
1985                     - (strlen(im->title)*LargeFont->w)/2,
1986                   8,
1987                     (unsigned char *)im->title, graph_col[GRC_FONT].i);
1988     
1989     /* graph labels */
1990     if( !(im->extra_flags & NOLEGEND) ) {
1991       for(i=0;i<im->gdes_c;i++){
1992         if(im->gdes[i].legend[0] =='\0')
1993             continue;
1994         
1995         if(im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT){
1996             
1997             polyPoints[0].x = im->gdes[i].legloc.x;
1998             polyPoints[0].y = im->gdes[i].legloc.y+1;
1999             polyPoints[1].x = polyPoints[0].x+boxH;
2000             polyPoints[2].x = polyPoints[0].x+boxH;
2001             polyPoints[3].x = polyPoints[0].x;
2002             polyPoints[1].y = polyPoints[0].y;
2003             polyPoints[2].y = polyPoints[0].y+boxV;
2004             polyPoints[3].y = polyPoints[0].y+boxV;
2005             gdImageFilledPolygon(gif,polyPoints,4,im->gdes[i].col.i);
2006             gdImagePolygon(gif,polyPoints,4,graph_col[GRC_FRAME].i);
2007         
2008             gdImageString(gif, SmallFont,
2009                           polyPoints[0].x+boxH+6, 
2010                           polyPoints[0].y-1,
2011                           (unsigned char *)im->gdes[i].legend,
2012                           graph_col[GRC_FONT].i);
2013         } else {
2014             polyPoints[0].x = im->gdes[i].legloc.x;
2015             polyPoints[0].y = im->gdes[i].legloc.y;
2016             
2017             gdImageString(gif, SmallFont,
2018                           polyPoints[0].x, 
2019                           polyPoints[0].y,
2020                           (unsigned char *)im->gdes[i].legend,
2021                           graph_col[GRC_FONT].i);
2022         }
2023       }
2024     }
2025     
2026     
2027     gator(gif, (int) im->xgif-5, 5);
2028
2029 }
2030
2031
2032 gdImagePtr
2033 MkLineBrush(image_desc_t *im,long cosel, enum gf_en typsel){
2034   gdImagePtr brush;
2035   int pen;
2036   switch (typsel){
2037   case GF_LINE1:
2038     brush=gdImageCreate(1,1);
2039     break;
2040   case GF_LINE2:
2041     brush=gdImageCreate(2,2);
2042     break;
2043   case GF_LINE3:
2044     brush=gdImageCreate(3,3);
2045     break;
2046   default:
2047     return NULL;
2048   }
2049
2050   gdImageColorTransparent(brush, 
2051                           gdImageColorAllocate(brush, 0, 0, 0));
2052
2053   pen = gdImageColorAllocate(brush, 
2054                              im->gdes[cosel].col.red,
2055                              im->gdes[cosel].col.green,
2056                              im->gdes[cosel].col.blue);
2057     
2058   switch (typsel){
2059   case GF_LINE1:
2060     gdImageSetPixel(brush,0,0,pen);
2061     break;
2062   case GF_LINE2:
2063     gdImageSetPixel(brush,0,0,pen);
2064     gdImageSetPixel(brush,0,1,pen);
2065     gdImageSetPixel(brush,1,0,pen);
2066     gdImageSetPixel(brush,1,1,pen);
2067     break;
2068   case GF_LINE3:
2069     gdImageSetPixel(brush,1,0,pen);
2070     gdImageSetPixel(brush,0,1,pen);
2071     gdImageSetPixel(brush,1,1,pen);
2072     gdImageSetPixel(brush,2,1,pen);
2073     gdImageSetPixel(brush,1,2,pen);
2074     break;
2075   default:
2076     return NULL;
2077   }
2078   return brush;
2079 }
2080 /*****************************************************
2081  * lazy check make sure we rely need to create this graph
2082  *****************************************************/
2083
2084 int lazy_check(image_desc_t *im){
2085     FILE *fd = NULL;
2086         int size = 1;
2087     struct stat  gifstat;
2088     
2089     if (im->lazy == 0) return 0; /* no lazy option */
2090     if (stat(im->graphfile,&gifstat) != 0) 
2091       return 0; /* can't stat */
2092     /* one pixel in the existing graph is more then what we would
2093        change here ... */
2094     if (time(NULL) - gifstat.st_mtime > 
2095         (im->end - im->start) / im->xsize) 
2096       return 0;
2097     if ((fd = fopen(im->graphfile,"rb")) == NULL) 
2098       return 0; /* the file does not exist */
2099     switch (im->imgformat) {
2100     case IF_GIF:
2101            size = GifSize(fd,&(im->xgif),&(im->ygif));
2102            break;
2103     case IF_PNG:
2104            size = PngSize(fd,&(im->xgif),&(im->ygif));
2105            break;
2106     }
2107     fclose(fd);
2108     return size;
2109 }
2110
2111 /* draw that picture thing ... */
2112 int
2113 graph_paint(image_desc_t *im, char ***calcpr)
2114 {
2115     int i,ii;
2116     int lazy =     lazy_check(im);
2117     FILE  *fo;
2118     
2119     /* gif stuff */
2120     gdImagePtr  gif,brush;
2121
2122     double areazero = 0.0;
2123     enum gf_en stack_gf = GF_PRINT;
2124     graph_desc_t *lastgdes = NULL;    
2125     gdPoint canvas[4], back[4];  /* points for canvas*/
2126
2127     /* if we are lazy and there is nothing to PRINT ... quit now */
2128     if (lazy && im->prt_c==0) return 0;
2129     
2130     /* pull the data from the rrd files ... */
2131     
2132     if(data_fetch(im)==-1)
2133         return -1;
2134
2135     /* evaluate VDEF and CDEF operations ... */
2136     if(data_calc(im)==-1)
2137         return -1;
2138
2139     /* calculate and PRINT and GPRINT definitions. We have to do it at
2140      * this point because it will affect the length of the legends
2141      * if there are no graph elements we stop here ... 
2142      * if we are lazy, try to quit ... 
2143      */
2144     i=print_calc(im,calcpr);
2145     if(i<0) return -1;
2146     if(i==0 || lazy) return 0;
2147
2148     /* get actual drawing data and find min and max values*/
2149     if(data_proc(im)==-1)
2150         return -1;
2151
2152     if(!im->logarithmic){si_unit(im);}        /* identify si magnitude Kilo, Mega Giga ? */
2153
2154     if(!im->rigid && ! im->logarithmic)
2155         expand_range(im);   /* make sure the upper and lower limit are
2156                            sensible values */
2157
2158     /* init xtr and ytr */
2159     /* determine the actual size of the gif to draw. The size given
2160        on the cmdline is the graph area. But we need more as we have
2161        draw labels and other things outside the graph area */
2162
2163
2164     im->xorigin = 10 + 9 * SmallFont->w+SmallFont->h;
2165     xtr(im,0); 
2166
2167     im->yorigin = 14 + im->ysize;
2168     ytr(im,DNAN);
2169
2170     if(im->title[0] != '\0')
2171         im->yorigin += (LargeFont->h+4);
2172
2173     im->xgif=20+im->xsize + im->xorigin;
2174     im->ygif= im->yorigin+2*SmallFont->h;
2175     
2176     /* determine where to place the legends onto the graphics.
2177        and set im->ygif to match space requirements for text */
2178     if(leg_place(im)==-1)
2179      return -1;
2180
2181     gif=gdImageCreate(im->xgif,im->ygif);
2182
2183     gdImageInterlace(gif, im->interlaced);
2184     
2185     /* allocate colors for the screen elements */
2186     for(i=0;i<DIM(graph_col);i++)
2187         /* check for user override values */
2188         if(im->graph_col[i].red != -1)
2189             graph_col[i].i = 
2190                 gdImageColorAllocate( gif,
2191                                       im->graph_col[i].red, 
2192                                       im->graph_col[i].green, 
2193                                       im->graph_col[i].blue);
2194         else
2195             graph_col[i].i = 
2196                 gdImageColorAllocate( gif,
2197                                       graph_col[i].red, 
2198                                       graph_col[i].green, 
2199                                       graph_col[i].blue);
2200         
2201     
2202     /* allocate colors for the graph */
2203     for(i=0;i<im->gdes_c;i++)
2204         /* only for elements which have a color defined */
2205         if (im->gdes[i].col.red != -1)
2206             im->gdes[i].col.i = 
2207                 gdImageColorAllocate(gif,
2208                                      im->gdes[i].col.red,
2209                                      im->gdes[i].col.green,
2210                                      im->gdes[i].col.blue);
2211     
2212     
2213     /* the actual graph is created by going through the individual
2214        graph elements and then drawing them */
2215     
2216     back[0].x = 0;
2217     back[0].y = 0;
2218     back[1].x = back[0].x+im->xgif;
2219     back[1].y = back[0].y;
2220     back[2].x = back[1].x;
2221     back[2].y = back[0].y+im->ygif;
2222     back[3].x = back[0].x;
2223     back[3].y = back[2].y;
2224
2225     gdImageFilledPolygon(gif,back,4,graph_col[GRC_BACK].i);
2226
2227     canvas[0].x = im->xorigin;
2228     canvas[0].y = im->yorigin;
2229     canvas[1].x = canvas[0].x+im->xsize;
2230     canvas[1].y = canvas[0].y;
2231     canvas[2].x = canvas[1].x;
2232     canvas[2].y = canvas[0].y-im->ysize;
2233     canvas[3].x = canvas[0].x;
2234     canvas[3].y = canvas[2].y;
2235
2236     gdImageFilledPolygon(gif,canvas,4,graph_col[GRC_CANVAS].i);
2237
2238     if (im->minval > 0.0)
2239         areazero = im->minval;
2240     if (im->maxval < 0.0)
2241         areazero = im->maxval;
2242
2243     axis_paint(im,gif);
2244
2245     for(i=0;i<im->gdes_c;i++){  
2246         
2247         switch(im->gdes[i].gf){
2248         case GF_CDEF:
2249         case GF_VDEF:
2250         case GF_DEF:
2251         case GF_PRINT:
2252         case GF_GPRINT:
2253         case GF_COMMENT:
2254         case GF_HRULE:
2255         case GF_VRULE:
2256                 break;
2257         case GF_TICK:
2258                 for (ii = 0; ii < im->xsize; ii++)
2259                 {
2260                    if (!isnan(im->gdes[i].p_data[ii]) && 
2261                            im->gdes[i].p_data[ii] > 0.0)
2262                    { 
2263                           /* generate a tick */
2264                           gdImageLine(gif, im -> xorigin + ii, 
2265                                  im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2266                                  im -> xorigin + ii, 
2267                                  im -> yorigin,
2268                                  im -> gdes[i].col.i);
2269                    }
2270                 }
2271                 break;
2272         case GF_LINE1:
2273         case GF_LINE2:
2274         case GF_LINE3:
2275         case GF_AREA:
2276             stack_gf = im->gdes[i].gf;
2277         case GF_STACK:      
2278             /* fix data points at oo and -oo */
2279             for(ii=0;ii<im->xsize;ii++){
2280                 if (isinf(im->gdes[i].p_data[ii])){
2281                     if (im->gdes[i].p_data[ii] > 0) {
2282                         im->gdes[i].p_data[ii] = im->maxval ;
2283                     } else {
2284                         im->gdes[i].p_data[ii] = im->minval ;
2285                     }               
2286                 
2287                 }
2288             }
2289
2290             if (im->gdes[i].col.i != -1){               
2291                /* GF_LINE and frined */
2292                if(stack_gf == GF_LINE1 || stack_gf == GF_LINE2 || stack_gf == GF_LINE3 ){
2293                    brush = MkLineBrush(im,i,stack_gf);
2294                    gdImageSetBrush(gif, brush);
2295                    for(ii=1;ii<im->xsize;ii++){
2296                        if (isnan(im->gdes[i].p_data[ii-1]) ||
2297                            isnan(im->gdes[i].p_data[ii]))
2298                             continue;
2299                        gdImageLine(gif,
2300                                     ii+im->xorigin-1,ytr(im,im->gdes[i].p_data[ii-1]),
2301                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2302                                     gdBrushed);
2303                         
2304                     }
2305                     gdImageDestroy(brush);
2306                 }
2307                 else 
2308                     /* GF_AREA STACK type*/
2309                     if (im->gdes[i].gf == GF_STACK )
2310                         for(ii=0;ii<im->xsize;ii++){
2311                             if(isnan(im->gdes[i].p_data[ii])){
2312                                 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2313                                 continue;
2314                             }
2315                             
2316                             if (lastgdes->p_data[ii] == im->gdes[i].p_data[ii]){
2317                                 continue;
2318                             }
2319                             gdImageLine(gif,
2320                                         ii+im->xorigin,ytr(im,lastgdes->p_data[ii]),
2321                                         ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2322                                         im->gdes[i].col.i);
2323                         }
2324                
2325                     else /* simple GF_AREA */
2326                         for(ii=0;ii<im->xsize;ii++){
2327                             if (isnan(im->gdes[i].p_data[ii])) {
2328                                 im->gdes[i].p_data[ii] = 0;
2329                                 continue;
2330                             }
2331                             gdImageLine(gif,
2332                                         ii+im->xorigin,ytr(im,areazero),
2333                                         ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2334                                         im->gdes[i].col.i);
2335                         }
2336            }
2337            lastgdes = &(im->gdes[i]);              
2338            break;
2339         }
2340     }
2341     
2342     grid_paint(im,gif);
2343
2344     /* the RULES are the last thing to paint ... */
2345     for(i=0;i<im->gdes_c;i++){  
2346         
2347         switch(im->gdes[i].gf){
2348         case GF_HRULE:
2349 printf("DEBUG: HRULE at %f\n",im->gdes[i].yrule);
2350             if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2351                 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2352             };
2353             if(im->gdes[i].yrule >= im->minval
2354                && im->gdes[i].yrule <= im->maxval)
2355               gdImageLine(gif,
2356                           im->xorigin,ytr(im,im->gdes[i].yrule),
2357                           im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2358                           im->gdes[i].col.i); 
2359             break;
2360         case GF_VRULE:
2361             if(im->gdes[i].xrule == 0) { /* fetch variable */
2362                 im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2363             };
2364             if(im->gdes[i].xrule >= im->start
2365                         && im->gdes[i].xrule <= im->end)
2366                 gdImageLine(gif,
2367                         xtr(im,im->gdes[i].xrule),im->yorigin,
2368                         xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2369                         im->gdes[i].col.i); 
2370             break;
2371         default:
2372             break;
2373         }
2374     }
2375
2376     if (strcmp(im->graphfile,"-")==0) {
2377 #ifdef WIN32
2378         /* Change translation mode for stdout to BINARY */
2379         _setmode( _fileno( stdout ), O_BINARY );
2380 #endif
2381         fo = stdout;
2382     } else {
2383         if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2384             rrd_set_error("Opening '%s' for write: %s",im->graphfile, strerror(errno));
2385             return (-1);
2386         }
2387     }
2388     switch (im->imgformat) {
2389     case IF_GIF:
2390         gdImageGif(gif, fo);    
2391         break;
2392     case IF_PNG:
2393         gdImagePng(gif, fo);    
2394         break;
2395     }
2396     if (strcmp(im->graphfile,"-") != 0)
2397         fclose(fo);
2398     gdImageDestroy(gif);
2399
2400     return 0;
2401 }
2402
2403
2404 /*****************************************************
2405  * graph stuff 
2406  *****************************************************/
2407
2408 int
2409 gdes_alloc(image_desc_t *im){
2410
2411     long def_step = (im->end-im->start)/im->xsize;
2412     
2413     if (im->step > def_step) /* step can be increassed ... no decreassed */
2414       def_step = im->step;
2415
2416     im->gdes_c++;
2417     
2418     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2419                                            * sizeof(graph_desc_t)))==NULL){
2420         rrd_set_error("realloc graph_descs");
2421         return -1;
2422     }
2423
2424
2425     im->gdes[im->gdes_c-1].step=def_step; 
2426     im->gdes[im->gdes_c-1].start=im->start; 
2427     im->gdes[im->gdes_c-1].end=im->end; 
2428     im->gdes[im->gdes_c-1].vname[0]='\0'; 
2429     im->gdes[im->gdes_c-1].data=NULL;
2430     im->gdes[im->gdes_c-1].ds_namv=NULL;
2431     im->gdes[im->gdes_c-1].data_first=0;
2432     im->gdes[im->gdes_c-1].p_data=NULL;
2433     im->gdes[im->gdes_c-1].rpnp=NULL;
2434     im->gdes[im->gdes_c-1].col.red = -1;
2435     im->gdes[im->gdes_c-1].col.i=-1;
2436     im->gdes[im->gdes_c-1].legend[0]='\0';
2437     im->gdes[im->gdes_c-1].rrd[0]='\0';
2438     im->gdes[im->gdes_c-1].ds=-1;    
2439     im->gdes[im->gdes_c-1].p_data=NULL;    
2440     return 0;
2441 }
2442
2443 /* copies input untill the first unescaped colon is found
2444    or until input ends. backslashes have to be escaped as well */
2445 int
2446 scan_for_col(char *input, int len, char *output)
2447 {
2448     int inp,outp=0;
2449     for (inp=0; 
2450          inp < len &&
2451            input[inp] != ':' &&
2452            input[inp] != '\0';
2453          inp++){
2454       if (input[inp] == '\\' &&
2455           input[inp+1] != '\0' && 
2456           (input[inp+1] == '\\' ||
2457            input[inp+1] == ':')){
2458         output[outp++] = input[++inp];
2459       }
2460       else {
2461         output[outp++] = input[inp];
2462       }
2463     }
2464     output[outp] = '\0';
2465     return inp;
2466 }
2467
2468 int 
2469 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2470 {
2471     
2472     image_desc_t   im;
2473     int            i;
2474     long           long_tmp;
2475     time_t         start_tmp=0,end_tmp=0;
2476     char           scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2477     char           symname[100];
2478     unsigned int            col_red,col_green,col_blue;
2479     long           scancount;
2480     int linepass = 0; /* stack can only follow directly after LINE* AREA or STACK */    
2481     struct time_value start_tv, end_tv;
2482     char *parsetime_error = NULL;
2483     int stroff;    
2484
2485     (*prdata)=NULL;
2486
2487     parsetime("end-24h", &start_tv);
2488     parsetime("now", &end_tv);
2489
2490     im.xlab_user.minsec = -1;
2491     im.xgif=0;
2492     im.ygif=0;
2493     im.xsize = 400;
2494     im.ysize = 100;
2495     im.step = 0;
2496     im.ylegend[0] = '\0';
2497     im.title[0] = '\0';
2498     im.minval = DNAN;
2499     im.maxval = DNAN;    
2500     im.interlaced = 0;
2501     im.unitsexponent= 9999;
2502     im.extra_flags= 0;
2503     im.rigid = 0;
2504     im.imginfo = NULL;
2505     im.lazy = 0;
2506     im.logarithmic = 0;
2507     im.ygridstep = DNAN;
2508     im.draw_x_grid = 1;
2509     im.draw_y_grid = 1;
2510     im.base = 1000;
2511     im.prt_c = 0;
2512     im.gdes_c = 0;
2513     im.gdes = NULL;
2514     im.imgformat = IF_GIF; /* we default to GIF output */
2515
2516     for(i=0;i<DIM(graph_col);i++)
2517         im.graph_col[i].red=-1;
2518     
2519     
2520     while (1){
2521         static struct option long_options[] =
2522         {
2523             {"start",      required_argument, 0,  's'},
2524             {"end",        required_argument, 0,  'e'},
2525             {"x-grid",     required_argument, 0,  'x'},
2526             {"y-grid",     required_argument, 0,  'y'},
2527             {"vertical-label",required_argument,0,'v'},
2528             {"width",      required_argument, 0,  'w'},
2529             {"height",     required_argument, 0,  'h'},
2530             {"interlaced", no_argument,       0,  'i'},
2531             {"upper-limit",required_argument, 0,  'u'},
2532             {"lower-limit",required_argument, 0,  'l'},
2533             {"rigid",      no_argument,       0,  'r'},
2534             {"base",       required_argument, 0,  'b'},
2535             {"logarithmic",no_argument,       0,  'o'},
2536             {"color",      required_argument, 0,  'c'},
2537             {"title",      required_argument, 0,  't'},
2538             {"imginfo",    required_argument, 0,  'f'},
2539             {"imgformat",  required_argument, 0,  'a'},
2540             {"lazy",       no_argument,       0,  'z'},
2541             {"no-legend",  no_argument,       0,  'g'},
2542             {"alt-y-grid", no_argument,       0,   257 },
2543             {"alt-autoscale", no_argument,    0,   258 },
2544             {"alt-autoscale-max", no_argument,    0,   259 },
2545             {"units-exponent",required_argument, 0,  260},
2546             {"step",       required_argument, 0,   261},
2547             {0,0,0,0}};
2548         int option_index = 0;
2549         int opt;
2550
2551         
2552         opt = getopt_long(argc, argv, 
2553                           "s:e:x:y:v:w:h:iu:l:rb:oc:t:f:a:z:g",
2554                           long_options, &option_index);
2555
2556         if (opt == EOF)
2557             break;
2558         
2559         switch(opt) {
2560         case 257:
2561             im.extra_flags |= ALTYGRID;
2562             break;
2563         case 258:
2564             im.extra_flags |= ALTAUTOSCALE;
2565             break;
2566         case 259:
2567             im.extra_flags |= ALTAUTOSCALE_MAX;
2568             break;
2569         case 'g':
2570             im.extra_flags |= NOLEGEND;
2571             break;
2572         case 260:
2573             im.unitsexponent = atoi(optarg);
2574             break;
2575         case 261:
2576             im.step =  atoi(optarg);
2577             break;
2578         case 's':
2579             if ((parsetime_error = parsetime(optarg, &start_tv))) {
2580                 rrd_set_error( "start time: %s", parsetime_error );
2581                 return -1;
2582             }
2583             break;
2584         case 'e':
2585             if ((parsetime_error = parsetime(optarg, &end_tv))) {
2586                 rrd_set_error( "end time: %s", parsetime_error );
2587                 return -1;
2588             }
2589             break;
2590         case 'x':
2591             if(strcmp(optarg,"none") == 0){
2592               im.draw_x_grid=0;
2593               break;
2594             };
2595                 
2596             if(sscanf(optarg,
2597                       "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2598                       scan_gtm,
2599                       &im.xlab_user.gridst,
2600                       scan_mtm,
2601                       &im.xlab_user.mgridst,
2602                       scan_ltm,
2603                       &im.xlab_user.labst,
2604                       &im.xlab_user.precis,
2605                       &stroff) == 7 && stroff != 0){
2606                 strncpy(im.xlab_form, optarg+stroff, sizeof(im.xlab_form) - 1);
2607                 if((im.xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2608                     rrd_set_error("unknown keyword %s",scan_gtm);
2609                     return -1;
2610                 } else if ((im.xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2611                     rrd_set_error("unknown keyword %s",scan_mtm);
2612                     return -1;
2613                 } else if ((im.xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2614                     rrd_set_error("unknown keyword %s",scan_ltm);
2615                     return -1;
2616                 } 
2617                 im.xlab_user.minsec = 1;
2618                 im.xlab_user.stst = im.xlab_form;
2619             } else {
2620                 rrd_set_error("invalid x-grid format");
2621                 return -1;
2622             }
2623             break;
2624         case 'y':
2625
2626             if(strcmp(optarg,"none") == 0){
2627               im.draw_y_grid=0;
2628               break;
2629             };
2630
2631             if(sscanf(optarg,
2632                       "%lf:%d",
2633                       &im.ygridstep,
2634                       &im.ylabfact) == 2) {
2635                 if(im.ygridstep<=0){
2636                     rrd_set_error("grid step must be > 0");
2637                     return -1;
2638                 } else if (im.ylabfact < 1){
2639                     rrd_set_error("label factor must be > 0");
2640                     return -1;
2641                 } 
2642             } else {
2643                 rrd_set_error("invalid y-grid format");
2644                 return -1;
2645             }
2646             break;
2647         case 'v':
2648             strncpy(im.ylegend,optarg,150);
2649             im.ylegend[150]='\0';
2650             break;
2651         case 'u':
2652             im.maxval = atof(optarg);
2653             break;
2654         case 'l':
2655             im.minval = atof(optarg);
2656             break;
2657         case 'b':
2658             im.base = atol(optarg);
2659             if(im.base != 1024 && im.base != 1000 ){
2660                 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2661                 return -1;
2662             }
2663             break;
2664         case 'w':
2665             long_tmp = atol(optarg);
2666             if (long_tmp < 10) {
2667                 rrd_set_error("width below 10 pixels");
2668                 return -1;
2669             }
2670             im.xsize = long_tmp;
2671             break;
2672         case 'h':
2673             long_tmp = atol(optarg);
2674             if (long_tmp < 10) {
2675                 rrd_set_error("height below 10 pixels");
2676                 return -1;
2677             }
2678             im.ysize = long_tmp;
2679             break;
2680         case 'i':
2681             im.interlaced = 1;
2682             break;
2683         case 'r':
2684             im.rigid = 1;
2685             break;
2686         case 'f':
2687             im.imginfo = optarg;
2688             break;
2689         case 'a':
2690             if((im.imgformat = if_conv(optarg)) == -1) {
2691                 rrd_set_error("unsupported graphics format '%s'",optarg);
2692                 return -1;
2693             }
2694             break;
2695         case 'z':
2696             im.lazy = 1;
2697             break;
2698         case 'o':
2699             im.logarithmic = 1;
2700             if (isnan(im.minval))
2701                 im.minval=1;
2702             break;
2703         case 'c':
2704             if(sscanf(optarg,
2705                       "%10[A-Z]#%2x%2x%2x",
2706                       col_nam,&col_red,&col_green,&col_blue) == 4){
2707                 int ci;
2708                 if((ci=grc_conv(col_nam)) != -1){
2709                     im.graph_col[ci].red=col_red;
2710                     im.graph_col[ci].green=col_green;
2711                     im.graph_col[ci].blue=col_blue;
2712                 }  else {
2713                   rrd_set_error("invalid color name '%s'",col_nam);
2714                 }
2715             } else {
2716                 rrd_set_error("invalid color def format");
2717                 return -1;
2718             }
2719             break;        
2720         case 't':
2721             strncpy(im.title,optarg,150);
2722             im.title[150]='\0';
2723             break;
2724
2725         case '?':
2726             if (optopt != 0)
2727                 rrd_set_error("unknown option '%c'", optopt);
2728             else
2729                 rrd_set_error("unknown option '%s'",argv[optind-1]);
2730             return -1;
2731         }
2732     }
2733     
2734     if (optind >= argc) {
2735        rrd_set_error("missing filename");
2736        return -1;
2737     }
2738
2739     if (im.logarithmic == 1 && (im.minval <= 0 || isnan(im.minval))){
2740         rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");    
2741         return -1;
2742     }
2743
2744     strncpy(im.graphfile,argv[optind],MAXPATH-1);
2745     im.graphfile[MAXPATH-1]='\0';
2746
2747     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2748         return -1;
2749     }  
2750     
2751     if (start_tmp < 3600*24*365*10){
2752         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2753         return -1;
2754     }
2755     
2756     if (end_tmp < start_tmp) {
2757         rrd_set_error("start (%ld) should be less than end (%ld)", 
2758                start_tmp, end_tmp);
2759         return -1;
2760     }
2761     
2762     im.start = start_tmp;
2763     im.end = end_tmp;
2764
2765     
2766     for(i=optind+1;i<argc;i++){
2767         int   argstart=0;
2768         int   strstart=0;
2769         char  varname[30],*rpnex;
2770         gdes_alloc(&im);
2771         if(sscanf(argv[i],"%10[A-Z0-9]:%n",symname,&argstart)==1){
2772             if((im.gdes[im.gdes_c-1].gf=gf_conv(symname))==-1){
2773                 im_free(&im);
2774                 rrd_set_error("unknown function '%s'",symname);
2775                 return -1;
2776             }
2777         } else {
2778             rrd_set_error("can't parse '%s'",argv[i]);
2779             im_free(&im);
2780             return -1;
2781         }
2782
2783         /* reset linepass if a non LINE/STACK/AREA operator gets parsed 
2784         
2785            if (im.gdes[im.gdes_c-1].gf != GF_LINE1 &&
2786            im.gdes[im.gdes_c-1].gf != GF_LINE2 &&
2787            im.gdes[im.gdes_c-1].gf != GF_LINE3 &&
2788            im.gdes[im.gdes_c-1].gf != GF_AREA &&
2789            im.gdes[im.gdes_c-1].gf != GF_STACK) {
2790            linepass = 0;
2791            } 
2792         */
2793         
2794         switch(im.gdes[im.gdes_c-1].gf){
2795         case GF_PRINT:
2796             im.prt_c++;
2797         case GF_GPRINT:
2798             if(sscanf(
2799                 &argv[i][argstart],
2800                 "%29[^#:]:" CF_NAM_FMT ":%n",
2801                 varname,symname,&strstart) == 2){
2802                 scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].format);
2803                 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
2804                     im_free(&im);
2805                     rrd_set_error("unknown variable '%s'",varname);
2806                     return -1;
2807                 }       
2808                 if((im.gdes[im.gdes_c-1].cf=cf_conv(symname))==-1){
2809                     im_free(&im);
2810                     return -1;
2811                 }
2812                 
2813             } else {
2814                 im_free(&im);
2815                 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2816                 return -1;
2817             }
2818             break;
2819         case GF_COMMENT:
2820             if(strlen(&argv[i][argstart])>FMT_LEG_LEN) argv[i][argstart+FMT_LEG_LEN-3]='\0' ;
2821             strcpy(im.gdes[im.gdes_c-1].legend, &argv[i][argstart]);
2822             break;
2823         case GF_HRULE:
2824             /* scan for either "HRULE:vname#..." or "HRULE:num#..."
2825              *
2826              * If a vname is used, the value NaN is set; this is catched
2827              * when graphing.  Setting value NaN from the script is not
2828              * permitted
2829              */
2830             strstart=0;
2831             sscanf(&argv[i][argstart], "%lf#%n"
2832                 ,&im.gdes[im.gdes_c-1].yrule
2833                 ,&strstart
2834             );
2835             if (strstart==0) { /* no number, should be vname */
2836                 sscanf(&argv[i][argstart], DEF_NAM_FMT "#%n"
2837                     ,varname
2838                     ,&strstart
2839                 );
2840                 if (strstart) {
2841                     im.gdes[im.gdes_c-1].yrule = DNAN;/* signal use of vname */
2842                     if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
2843                         im_free(&im);
2844                         rrd_set_error("unknown variable '%s' in HRULE",varname);
2845                         return -1;
2846                     }           
2847                     if(im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_VDEF) {
2848                         im_free(&im);
2849                         rrd_set_error("Only VDEF is allowed in HRULE",varname);
2850                         return -1;
2851                     }
2852                 }
2853             } else {
2854 printf("DEBUG: matched HRULE:num\n");
2855 printf("DEBUG: strstart==%i\n",strstart);
2856             };
2857             if (strstart==0) {
2858                 im_free(&im);
2859                 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2860                 return -1;
2861             } else {
2862                 int n=0;
2863                 if(sscanf(
2864                         &argv[i][argstart+strstart],
2865                         "%2x%2x%2x:%n",
2866                         &col_red,
2867                         &col_green,
2868                         &col_blue,
2869                         &n)>=3) {
2870                     im.gdes[im.gdes_c-1].col.red = col_red;
2871                     im.gdes[im.gdes_c-1].col.green = col_green;
2872                     im.gdes[im.gdes_c-1].col.blue = col_blue;
2873                     if (n==0) {
2874                         im.gdes[im.gdes_c-1].legend[0] = '\0';
2875                     } else {
2876                         scan_for_col(&argv[i][argstart+strstart+n],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
2877                     }
2878                 } else {
2879                     im_free(&im);
2880                     rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2881                     return -1;
2882                 }
2883             }
2884             
2885             break;
2886         case GF_VRULE:
2887             /* scan for either "VRULE:vname#..." or "VRULE:num#..."
2888              *
2889              * If a vname is used, the value 0 is set; this is catched
2890              * when graphing.  Setting value 0 from the script is not
2891              * permitted
2892              */
2893             strstart=0;
2894             sscanf(&argv[i][argstart], "%lu#%n"
2895                 ,(long unsigned int *)&im.gdes[im.gdes_c-1].xrule
2896                 ,&strstart
2897             );
2898             if (strstart==0) { /* no number, should be vname */
2899                 sscanf(&argv[i][argstart], DEF_NAM_FMT "#%n"
2900                     ,varname
2901                     ,&strstart
2902                 );
2903                 if (strstart!=0) { /* vname matched */
2904                     im.gdes[im.gdes_c-1].xrule = 0;/* signal use of vname */
2905                     if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
2906                         im_free(&im);
2907                         rrd_set_error("unknown variable '%s' in VRULE",varname);
2908                         return -1;
2909                     }           
2910                     if(im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_VDEF) {
2911                         im_free(&im);
2912                         rrd_set_error("Only VDEF is allowed in VRULE",varname);
2913                         return -1;
2914                     }
2915                 }
2916             } else {
2917                 if (im.gdes[im.gdes_c-1].xrule==0)
2918                     strstart=0;
2919             }
2920
2921             if (strstart==0) {
2922                 im_free(&im);
2923                 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2924                 return -1;
2925             } else {
2926                 int n=0;
2927                 if(sscanf(
2928                         &argv[i][argstart+strstart],
2929                         "%2x%2x%2x:%n",
2930                         &col_red,
2931                         &col_green,
2932                         &col_blue,
2933                         &n)>=3) {
2934                     im.gdes[im.gdes_c-1].col.red = col_red;
2935                     im.gdes[im.gdes_c-1].col.green = col_green;
2936                     im.gdes[im.gdes_c-1].col.blue = col_blue;
2937                     if (n==0) {
2938                         im.gdes[im.gdes_c-1].legend[0] = '\0';
2939                     } else {
2940                         scan_for_col(&argv[i][argstart+strstart+n],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
2941                     }
2942                 } else {
2943                     im_free(&im);
2944                     rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2945                     return -1;
2946                 }
2947             }
2948             break;
2949         case GF_TICK:
2950             if((scancount=sscanf(
2951                 &argv[i][argstart],
2952                 "%29[^:#]#%2x%2x%2x:%lf:%n",
2953                 varname,
2954                 &col_red,
2955                 &col_green,
2956                 &col_blue,
2957                 &(im.gdes[im.gdes_c-1].yrule),
2958                 &strstart))>=1)
2959                 {
2960                 im.gdes[im.gdes_c-1].col.red = col_red;
2961                 im.gdes[im.gdes_c-1].col.green = col_green;
2962                 im.gdes[im.gdes_c-1].col.blue = col_blue;
2963                 if(strstart <= 0){
2964                     im.gdes[im.gdes_c-1].legend[0] = '\0';
2965                 } else { 
2966                     scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
2967                 }
2968                 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
2969                     im_free(&im);
2970                     rrd_set_error("unknown variable '%s'",varname);
2971                     return -1;
2972                 }
2973                 if (im.gdes[im.gdes_c-1].yrule <= 0.0 || im.gdes[im.gdes_c-1].yrule > 1.0)
2974                 {
2975                     im_free(&im);
2976                     rrd_set_error("Tick mark scaling factor out of range");
2977                     return -1;
2978                 }
2979                 if (scancount < 4)
2980                    im.gdes[im.gdes_c-1].col.red = -1;           
2981             if (scancount < 5) 
2982                    /* default tick marks: 10% of the y-axis */
2983                    im.gdes[im.gdes_c-1].yrule = 0.1;
2984
2985                 } else {
2986                    im_free(&im);
2987                    rrd_set_error("can't parse '%s'",&argv[i][argstart]);
2988                    return -1;
2989                 } /* endif sscanf */
2990                 break;
2991         case GF_STACK:
2992             if(linepass == 0){
2993                 im_free(&im);
2994                 rrd_set_error("STACK must follow AREA, LINE or STACK");
2995                 return -1; 
2996             }           
2997         case GF_LINE1:
2998         case GF_LINE2:
2999         case GF_LINE3:
3000         case GF_AREA:
3001             linepass = 1;
3002             if((scancount=sscanf(
3003                 &argv[i][argstart],
3004                 "%29[^:#]#%2x%2x%2x:%n",
3005                 varname,
3006                 &col_red,
3007                 &col_green,
3008                     &col_blue,
3009                 &strstart))>=1){
3010                 im.gdes[im.gdes_c-1].col.red = col_red;
3011                 im.gdes[im.gdes_c-1].col.green = col_green;
3012                 im.gdes[im.gdes_c-1].col.blue = col_blue;
3013                 if(strstart <= 0){
3014                     im.gdes[im.gdes_c-1].legend[0] = '\0';
3015                 } else { 
3016                     scan_for_col(&argv[i][argstart+strstart],FMT_LEG_LEN,im.gdes[im.gdes_c-1].legend);
3017                 }
3018                 if((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname))==-1){
3019                     im_free(&im);
3020                     rrd_set_error("unknown variable '%s'",varname);
3021                     return -1;
3022                 }               
3023                 if (scancount < 4)
3024                     im.gdes[im.gdes_c-1].col.red = -1;          
3025                 
3026             } else {
3027                 im_free(&im);
3028                 rrd_set_error("can't parse '%s'",&argv[i][argstart]);
3029                 return -1;
3030             }
3031             break;
3032         case GF_CDEF:
3033             if((rpnex = malloc(strlen(&argv[i][argstart])*sizeof(char)))==NULL){
3034                 rrd_set_error("malloc for CDEF");
3035                 return -1;
3036             }
3037             if(sscanf(
3038                     &argv[i][argstart],
3039                     DEF_NAM_FMT "=%[^: ]",
3040                     im.gdes[im.gdes_c-1].vname,
3041                     rpnex) != 2){
3042                 im_free(&im);
3043                 free(rpnex);
3044                 rrd_set_error("can't parse CDEF '%s'",&argv[i][argstart]);
3045                 return -1;
3046             }
3047             /* checking for duplicate variable names */
3048             if(find_var(&im,im.gdes[im.gdes_c-1].vname) != -1){
3049                 im_free(&im);
3050                 rrd_set_error("duplicate variable '%s'",
3051                               im.gdes[im.gdes_c-1].vname);
3052                 return -1; 
3053             }      
3054             if((im.gdes[im.gdes_c-1].rpnp = 
3055                    rpn_parse((void*)&im,rpnex,&find_var_wrapper))== NULL){
3056                 rrd_set_error("invalid rpn expression '%s'", rpnex);
3057                 im_free(&im);           
3058                 return -1;
3059             }
3060             free(rpnex);
3061             break;
3062         case GF_VDEF:
3063             /*
3064              * strstart is set to zero and will NOT be changed
3065              * if the comma is not matched.  This means that it
3066              * remains zero. Although strstart is initialized to
3067              * zero at the beginning of this loop, we do it again
3068              * here just in case someone changes the code...
3069              *
3070              * According to the docs we cannot rely on the
3071              * returned value from sscanf; it can be 2 or 3,
3072              * depending on %n incrementing it or not.
3073              */
3074             strstart=0;
3075             sscanf(
3076                     &argv[i][argstart],
3077                     DEF_NAM_FMT "=" DEF_NAM_FMT ",%n",
3078                     im.gdes[im.gdes_c-1].vname,
3079                     varname,
3080                     &strstart);
3081             if (strstart){
3082                 /* checking both variable names */
3083                 if (find_var(&im,im.gdes[im.gdes_c-1].vname) != -1){
3084                     im_free(&im);
3085                     rrd_set_error("duplicate variable '%s'",
3086                                 im.gdes[im.gdes_c-1].vname);
3087                     return -1; 
3088                 } else {
3089                     if ((im.gdes[im.gdes_c-1].vidx=find_var(&im,varname)) == -1){
3090                         im_free(&im);
3091                         rrd_set_error("variable '%s' not known in VDEF '%s'",
3092                                 varname,
3093                                 im.gdes[im.gdes_c-1].vname);
3094                         return -1; 
3095                     } else {
3096                         if(im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_DEF
3097                         && im.gdes[im.gdes[im.gdes_c-1].vidx].gf != GF_CDEF){
3098                             rrd_set_error("variable '%s' not DEF nor CDEF in VDEF '%s'",
3099                                 varname,
3100                                 im.gdes[im.gdes_c-1].vname);
3101                             im_free(&im);
3102                             return -1; 
3103                         }
3104                     }
3105                     /* parsed upto and including the first comma. Now
3106                      * see what function is requested.  This function
3107                      * sets the error string.
3108                      */
3109                     if (vdef_parse(&im.gdes[im.gdes_c-1],&argv[i][argstart+strstart])<0) {
3110                         im_free(&im);
3111                         return -1;
3112                     };
3113                 }
3114             } else {
3115                 im_free(&im);
3116                 rrd_set_error("can't parse VDEF '%s'",&argv[i][argstart]);
3117                 return -1;
3118             }
3119             break;
3120         case GF_DEF:
3121             if (sscanf(
3122                 &argv[i][argstart],
3123                 DEF_NAM_FMT "=%n",
3124                 im.gdes[im.gdes_c-1].vname,
3125                 &strstart)== 1 && strstart){ /* is the = did not match %n returns 0 */ 
3126                 if(sscanf(&argv[i][argstart
3127                                   +strstart
3128                                   +scan_for_col(&argv[i][argstart+strstart],
3129                                                 MAXPATH,im.gdes[im.gdes_c-1].rrd)],
3130                           ":" DS_NAM_FMT ":" CF_NAM_FMT,
3131                           im.gdes[im.gdes_c-1].ds_nam,
3132                           symname) != 2){
3133                     im_free(&im);
3134                     rrd_set_error("can't parse DEF '%s' -2",&argv[i][argstart]);
3135                     return -1;
3136                 }
3137             } else {
3138                 im_free(&im);
3139                 rrd_set_error("can't parse DEF '%s'",&argv[i][argstart]);
3140                 return -1;
3141             }
3142             
3143             /* checking for duplicate DEF CDEFS */
3144             if (find_var(&im,im.gdes[im.gdes_c-1].vname) != -1){
3145                 im_free(&im);
3146                 rrd_set_error("duplicate variable '%s'",
3147                           im.gdes[im.gdes_c-1].vname);
3148                 return -1; 
3149             }      
3150             if((im.gdes[im.gdes_c-1].cf=cf_conv(symname))==-1){
3151                 im_free(&im);
3152                 rrd_set_error("unknown cf '%s'",symname);
3153                 return -1;
3154             }
3155             break;
3156         }
3157         
3158     }
3159
3160     if (im.gdes_c==0){
3161         rrd_set_error("can't make a graph without contents");
3162         im_free(&im);
3163         return(-1); 
3164     }
3165     
3166         /* parse rest of arguments containing information on what to draw*/
3167     if (graph_paint(&im,prdata)==-1){
3168         im_free(&im);
3169         return -1;
3170     }
3171     
3172     *xsize=im.xgif;
3173     *ysize=im.ygif;
3174     if (im.imginfo){
3175       char *filename;
3176       if (! (*prdata)) {        
3177         /* maybe prdata is not allocated yet ... lets do it now */
3178         if((*prdata = calloc(2,sizeof(char *)))==NULL){
3179           rrd_set_error("malloc imginfo");
3180           return -1; 
3181         };
3182       }
3183       if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3184          ==NULL){
3185         rrd_set_error("malloc imginfo");
3186         return -1;
3187       }
3188       filename=im.graphfile+strlen(im.graphfile);      
3189       while(filename > im.graphfile){
3190         if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3191         filename--;
3192       }
3193       
3194       sprintf((*prdata)[0],im.imginfo,filename,im.xgif,im.ygif);
3195     }
3196     im_free(&im);
3197     return 0;
3198 }
3199
3200 int bad_format(char *fmt) {
3201         char *ptr;
3202
3203         ptr = fmt;
3204         while (*ptr != '\0') {
3205                 if (*ptr == '%') {ptr++;
3206                         if (*ptr == '\0') return 1;
3207                         while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') { 
3208                                 ptr++;
3209                         }
3210                         if (*ptr == '\0') return 1;
3211                         if (*ptr == 'l') {
3212                                 ptr++;
3213                                 if (*ptr == '\0') return 1;
3214                                 if (*ptr == 'e' || *ptr == 'f') { 
3215                                         ptr++; 
3216                                         } else { return 1; }
3217                         }
3218                         else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3219                         else { return 1; }
3220                 } else {
3221                         ++ptr;
3222                 }
3223         }
3224         return 0;
3225 }
3226 int
3227 vdef_parse(gdes,str)
3228 struct graph_desc_t *gdes;
3229 char *str;
3230 {
3231     /* A VDEF currently is either "func" or "param,func"
3232      * so the parsing is rather simple.  Change if needed.
3233      */
3234     double      param;
3235     char        func[30];
3236     int         n;
3237     
3238     n=0;
3239     sscanf(str,"%le,%29[A-Z]%n",&param,func,&n);
3240     if (n==strlen(str)) { /* matched */
3241         ;
3242     } else {
3243         n=0;
3244         sscanf(str,"%29[A-Z]%n",func,&n);
3245         if (n==strlen(str)) { /* matched */
3246             param=DNAN;
3247         } else {
3248             rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3249                 ,str
3250                 ,gdes->vname
3251                 );
3252             return -1;
3253         }
3254     }
3255     if          (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3256     else if     (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3257     else if     (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3258     else if     (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3259     else if     (!strcmp("FIRST",  func)) gdes->vf.op = VDEF_FIRST;
3260     else if     (!strcmp("LAST",   func)) gdes->vf.op = VDEF_LAST;
3261     else {
3262         rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3263             ,func
3264             ,gdes->vname
3265             );
3266         return -1;
3267     };
3268
3269     switch (gdes->vf.op) {
3270         case VDEF_PERCENT:
3271             if (isnan(param)) { /* no parameter given */
3272                 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3273                     ,func
3274                     ,gdes->vname
3275                     );
3276                 return -1;
3277             };
3278             if (param>=0.0 && param<=100.0) {
3279                 gdes->vf.param = param;
3280                 gdes->vf.val   = DNAN;  /* undefined */
3281                 gdes->vf.when  = 0;     /* undefined */
3282             } else {
3283                 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3284                     ,param
3285                     ,gdes->vname
3286                     );
3287                 return -1;
3288             };
3289             break;
3290         case VDEF_MAXIMUM:
3291         case VDEF_AVERAGE:
3292         case VDEF_MINIMUM:
3293         case VDEF_FIRST:
3294         case VDEF_LAST:
3295             if (isnan(param)) {
3296                 gdes->vf.param = DNAN;
3297                 gdes->vf.val   = DNAN;
3298                 gdes->vf.when  = 0;
3299             } else {
3300                 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3301                     ,func
3302                     ,gdes->vname
3303                     );
3304                 return -1;
3305             };
3306             break;
3307     };
3308     return 0;
3309 }
3310 int
3311 vdef_calc(im,gdi)
3312 image_desc_t *im;
3313 int gdi;
3314 {
3315     graph_desc_t        *src,*dst;
3316     rrd_value_t         *data;
3317     long                step,steps;
3318
3319     dst = &im->gdes[gdi];
3320     src = &im->gdes[dst->vidx];
3321     data = src->data + src->ds + src->ds_cnt; /* skip first value! */
3322     steps = (src->end - src->start) / src->step;
3323
3324 #if 0
3325 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3326     ,src->start
3327     ,src->end
3328     ,steps
3329     );
3330 #endif
3331
3332     switch (im->gdes[gdi].vf.op) {
3333         case VDEF_PERCENT: {
3334                 rrd_value_t *   array;
3335                 int             field;
3336
3337
3338                 if ((array = malloc(steps*sizeof(double)))==NULL) {
3339                     rrd_set_error("malloc VDEV_PERCENT");
3340                     return -1;
3341                 }
3342                 for (step=0;step < steps; step++) {
3343                     array[step]=data[step*src->ds_cnt];
3344                 }
3345                 qsort(array,step,sizeof(double),vdef_percent_compar);
3346
3347                 field = (steps-1)*dst->vf.param/100;
3348                 dst->vf.val  = array[field];
3349                 dst->vf.when = 0;       /* no time component */
3350 #if 1
3351 for(step=0;step<steps;step++)
3352 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3353 #endif
3354             }
3355             break;
3356         case VDEF_MAXIMUM:
3357             step=0;
3358             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3359             if (step == steps) {
3360                 dst->vf.val  = DNAN;
3361                 dst->vf.when = 0;
3362             } else {
3363                 dst->vf.val  = data[steps*src->ds_cnt];
3364                 dst->vf.when = src->start + (step+1)*src->step;
3365             }
3366             while (step != steps) {
3367                 if (finite(data[step*src->ds_cnt])) {
3368                     if (data[step*src->ds_cnt] > dst->vf.val) {
3369                         dst->vf.val  = data[steps*src->ds_cnt];
3370                         dst->vf.when = src->start + (step+1)*src->step;
3371                     }
3372                 }
3373                 step++;
3374             }
3375             break;
3376         case VDEF_AVERAGE: {
3377             int cnt=0;
3378             double sum=0.0;
3379             for (step=0;step<steps;step++) {
3380                 if (finite(data[step*src->ds_cnt])) {
3381                     sum += data[step*src->ds_cnt];
3382                     cnt ++;
3383                 }
3384                 step++;
3385             }
3386             if (cnt) {
3387                 dst->vf.val  = sum/cnt;
3388                 dst->vf.when = 0;       /* no time component */
3389             } else {
3390                 dst->vf.val  = DNAN;
3391                 dst->vf.when = 0;
3392             }
3393             }
3394             break;
3395         case VDEF_MINIMUM:
3396             step=0;
3397             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3398             if (step == steps) {
3399                 dst->vf.val  = DNAN;
3400                 dst->vf.when = 0;
3401             } else {
3402                 dst->vf.val  = data[steps*src->ds_cnt];
3403                 dst->vf.when = src->start + (step+1)*src->step;
3404             }
3405             while (step != steps) {
3406                 if (finite(data[step*src->ds_cnt])) {
3407                     if (data[step*src->ds_cnt] < dst->vf.val) {
3408                         dst->vf.val  = data[steps*src->ds_cnt];
3409                         dst->vf.when = src->start + (step+1)*src->step;
3410                     }
3411                 }
3412                 step++;
3413             }
3414             break;
3415         case VDEF_FIRST:
3416             /* The time value returned here is one step before the
3417              * actual time value.  This is the start of the first
3418              * non-NaN interval.
3419              */
3420             step=0;
3421             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3422             if (step == steps) { /* all entries were NaN */
3423                 dst->vf.val  = DNAN;
3424                 dst->vf.when = 0;
3425             } else {
3426                 dst->vf.val  = data[step*src->ds_cnt];
3427                 dst->vf.when = src->start + step*src->step;
3428             }
3429             break;
3430         case VDEF_LAST:
3431             /* The time value returned here is the
3432              * actual time value.  This is the end of the last
3433              * non-NaN interval.
3434              */
3435             step=steps-1;
3436             while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3437             if (step < 0) { /* all entries were NaN */
3438                 dst->vf.val  = DNAN;
3439                 dst->vf.when = 0;
3440             } else {
3441                 dst->vf.val  = data[step*src->ds_cnt];
3442                 dst->vf.when = src->start + (step+1)*src->step;
3443             }
3444             break;
3445     }
3446     return 0;
3447 }
3448
3449 /* NaN <= -INF <= finite_values <= INF */
3450 int
3451 vdef_percent_compar(a,b)
3452 const void *a,*b;
3453 {
3454     /* Equality is not returned; this doesn't hurt except
3455      * (maybe) for a little performance.
3456      */
3457
3458     /* First catch NaN values. They are smallest */
3459     if (isnan( *(double *)a )) return -1;
3460     if (isnan( *(double *)b )) return  1;
3461
3462     /* NaN doestn't reach this part so INF and -INF are extremes.
3463      * The sign from isinf() is compatible with the sign we return
3464      */
3465     if (isinf( *(double *)a )) return isinf( *(double *)a );
3466     if (isinf( *(double *)b )) return isinf( *(double *)b );
3467
3468     /* If we reach this, both values must be finite */
3469     if ( *(double *)a < *(double *)b ) return -1; else return 1;
3470 }