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