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