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