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