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