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