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