04cdc5478e7fc6b68bb2c7649f89d40ca15ec7a4
[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                 if(steparray == NULL){
820                     rrd_set_error("rpn expressions without DEF"
821                                 " or CDEF variables are not supported");
822                     rpnstack_free(&rpnstack);
823                     return -1;    
824                 }
825                 steparray[stepcnt]=0;
826                 /* Now find the resulting step.  All steps in all
827                  * used RRAs have to be visited
828                  */
829                 im->gdes[gdi].step = lcd(steparray);
830                 free(steparray);
831                 if((im->gdes[gdi].data = malloc((
832                                 (im->gdes[gdi].end-im->gdes[gdi].start) 
833                                     / im->gdes[gdi].step)
834                                     * sizeof(double)))==NULL){
835                     rrd_set_error("malloc im->gdes[gdi].data");
836                     rpnstack_free(&rpnstack);
837                     return -1;
838                 }
839         
840                 /* Step through the new cdef results array and
841                  * calculate the values
842                  */
843                 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
844                                 now<=im->gdes[gdi].end;
845                                 now += im->gdes[gdi].step)
846                 {
847                     rpnp_t  *rpnp = im -> gdes[gdi].rpnp;
848
849                     /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
850                      * in this case we are advancing by timesteps;
851                      * we use the fact that time_t is a synonym for long
852                      */
853                     if (rpn_calc(rpnp,&rpnstack,(long) now, 
854                                 im->gdes[gdi].data,++dataidx) == -1) {
855                         /* rpn_calc sets the error string */
856                         rpnstack_free(&rpnstack); 
857                         return -1;
858                     } 
859                 } /* enumerate over time steps within a CDEF */
860                 break;
861             default:
862                 continue;
863         }
864     } /* enumerate over CDEFs */
865     rpnstack_free(&rpnstack);
866     return 0;
867 }
868
869 /* massage data so, that we get one value for each x coordinate in the graph */
870 int
871 data_proc( image_desc_t *im ){
872     long i,ii;
873     double pixstep = (double)(im->end-im->start)
874         /(double)im->xsize; /* how much time 
875                                passes in one pixel */
876     double paintval;
877     double minval=DNAN,maxval=DNAN;
878     
879     unsigned long gr_time;    
880
881     /* memory for the processed data */
882     for(i=0;i<im->gdes_c;i++){
883       if((im->gdes[i].gf==GF_LINE) ||
884          (im->gdes[i].gf==GF_AREA) ||
885          (im->gdes[i].gf==GF_TICK) ||
886          (im->gdes[i].gf==GF_STACK)){
887         if((im->gdes[i].p_data = malloc((im->xsize +1)
888                                         * sizeof(rrd_value_t)))==NULL){
889           rrd_set_error("malloc data_proc");
890           return -1;
891         }
892       }
893     }
894     
895     for(i=0;i<im->xsize;i++){
896         long vidx;
897         gr_time = im->start+pixstep*i; /* time of the 
898                                           current step */
899         paintval=0.0;
900         
901         for(ii=0;ii<im->gdes_c;ii++){
902           double value;
903             switch(im->gdes[ii].gf){
904             case GF_LINE:
905             case GF_AREA:
906                 case GF_TICK:
907                 paintval = 0.0;
908             case GF_STACK:
909                 vidx = im->gdes[ii].vidx;
910
911                 value =
912                     im->gdes[vidx].data[
913                         ((unsigned long)floor(
914         (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
915                                              )
916                         )       *im->gdes[vidx].ds_cnt
917                                 +im->gdes[vidx].ds];
918
919                 if (! isnan(value)) {
920                   paintval += value;
921                   im->gdes[ii].p_data[i] = paintval;
922                   /* GF_TICK: the data values are not relevant for min and max */
923                   if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
924                    if (isnan(minval) || paintval <  minval)
925                      minval = paintval;
926                    if (isnan(maxval) || paintval >  maxval)
927                      maxval = paintval;
928                   }
929                 } else {
930                   im->gdes[ii].p_data[i] = DNAN;
931                 }
932                 break;
933             case GF_PRINT:
934             case GF_GPRINT:
935             case GF_COMMENT:
936             case GF_HRULE:
937             case GF_VRULE:
938             case GF_DEF:               
939             case GF_CDEF:
940             case GF_VDEF:
941             case GF_PART:
942                 break;
943             }
944         }
945     }
946
947     /* if min or max have not been asigned a value this is because
948        there was no data in the graph ... this is not good ...
949        lets set these to dummy values then ... */
950
951     if (isnan(minval)) minval = 0.0;
952     if (isnan(maxval)) maxval = 1.0;
953     
954     /* adjust min and max values */
955     if (isnan(im->minval) 
956         || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
957             && im->minval > minval))
958         im->minval = minval;
959     if (isnan(im->maxval) 
960         || (!im->rigid 
961             && im->maxval < maxval)){
962         if (im->logarithmic)
963             im->maxval = maxval * 1.1;
964         else
965             im->maxval = maxval;
966     }
967     /* make sure min and max are not equal */
968     if (im->minval == im->maxval) {
969       im->maxval *= 1.01; 
970       if (! im->logarithmic) {
971         im->minval *= 0.99;
972       }
973       
974       /* make sure min and max are not both zero */
975       if (im->maxval == 0.0) {
976             im->maxval = 1.0;
977       }
978         
979     }
980     return 0;
981 }
982
983
984
985 /* identify the point where the first gridline, label ... gets placed */
986
987 time_t
988 find_first_time(
989     time_t   start, /* what is the initial time */
990     enum tmt_en baseint,  /* what is the basic interval */
991     long     basestep /* how many if these do we jump a time */
992     )
993 {
994     struct tm tm;
995     tm = *localtime(&start);
996     switch(baseint){
997     case TMT_SECOND:
998         tm.tm_sec -= tm.tm_sec % basestep; break;
999     case TMT_MINUTE: 
1000         tm.tm_sec=0;
1001         tm.tm_min -= tm.tm_min % basestep; 
1002         break;
1003     case TMT_HOUR:
1004         tm.tm_sec=0;
1005         tm.tm_min = 0;
1006         tm.tm_hour -= tm.tm_hour % basestep; break;
1007     case TMT_DAY:
1008         /* we do NOT look at the basestep for this ... */
1009         tm.tm_sec=0;
1010         tm.tm_min = 0;
1011         tm.tm_hour = 0; break;
1012     case TMT_WEEK:
1013         /* we do NOT look at the basestep for this ... */
1014         tm.tm_sec=0;
1015         tm.tm_min = 0;
1016         tm.tm_hour = 0;
1017         tm.tm_mday -= tm.tm_wday -1;    /* -1 because we want the monday */
1018         if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1019         break;
1020     case TMT_MONTH:
1021         tm.tm_sec=0;
1022         tm.tm_min = 0;
1023         tm.tm_hour = 0;
1024         tm.tm_mday = 1;
1025         tm.tm_mon -= tm.tm_mon % basestep; break;
1026
1027     case TMT_YEAR:
1028         tm.tm_sec=0;
1029         tm.tm_min = 0;
1030         tm.tm_hour = 0;
1031         tm.tm_mday = 1;
1032         tm.tm_mon = 0;
1033         tm.tm_year -= (tm.tm_year+1900) % basestep;
1034         
1035     }
1036     return mktime(&tm);
1037 }
1038 /* identify the point where the next gridline, label ... gets placed */
1039 time_t 
1040 find_next_time(
1041     time_t   current, /* what is the initial time */
1042     enum tmt_en baseint,  /* what is the basic interval */
1043     long     basestep /* how many if these do we jump a time */
1044     )
1045 {
1046     struct tm tm;
1047     time_t madetime;
1048     tm = *localtime(&current);
1049     do {
1050         switch(baseint){
1051         case TMT_SECOND:
1052             tm.tm_sec += basestep; break;
1053         case TMT_MINUTE: 
1054             tm.tm_min += basestep; break;
1055         case TMT_HOUR:
1056             tm.tm_hour += basestep; break;
1057         case TMT_DAY:
1058             tm.tm_mday += basestep; break;
1059         case TMT_WEEK:
1060             tm.tm_mday += 7*basestep; break;
1061         case TMT_MONTH:
1062             tm.tm_mon += basestep; break;
1063         case TMT_YEAR:
1064             tm.tm_year += basestep;     
1065         }
1066         madetime = mktime(&tm);
1067     } while (madetime == -1); /* this is necessary to skip impssible times
1068                                  like the daylight saving time skips */
1069     return madetime;
1070           
1071 }
1072
1073
1074 /* calculate values required for PRINT and GPRINT functions */
1075
1076 int
1077 print_calc(image_desc_t *im, char ***prdata) 
1078 {
1079     long i,ii,validsteps;
1080     double printval;
1081     time_t printtime;
1082     int graphelement = 0;
1083     long vidx;
1084     int max_ii; 
1085     double magfact = -1;
1086     char *si_symb = "";
1087     char *percent_s;
1088     int prlines = 1;
1089     if (im->imginfo) prlines++;
1090     for(i=0;i<im->gdes_c;i++){
1091         switch(im->gdes[i].gf){
1092         case GF_PRINT:
1093             prlines++;
1094             if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1095                 rrd_set_error("realloc prdata");
1096                 return 0;
1097             }
1098         case GF_GPRINT:
1099             /* PRINT and GPRINT can now print VDEF generated values.
1100              * There's no need to do any calculations on them as these
1101              * calculations were already made.
1102              */
1103             vidx = im->gdes[i].vidx;
1104             if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1105                 printval = im->gdes[vidx].vf.val;
1106                 printtime = im->gdes[vidx].vf.when;
1107             } else { /* need to calculate max,min,avg etcetera */
1108                 max_ii =((im->gdes[vidx].end 
1109                         - im->gdes[vidx].start)
1110                         / im->gdes[vidx].step
1111                         * im->gdes[vidx].ds_cnt);
1112                 printval = DNAN;
1113                 validsteps = 0;
1114                 for(    ii=im->gdes[vidx].ds;
1115                         ii < max_ii;
1116                         ii+=im->gdes[vidx].ds_cnt){
1117                     if (! finite(im->gdes[vidx].data[ii]))
1118                         continue;
1119                     if (isnan(printval)){
1120                         printval = im->gdes[vidx].data[ii];
1121                         validsteps++;
1122                         continue;
1123                     }
1124
1125                     switch (im->gdes[i].cf){
1126                         case CF_HWPREDICT:
1127                         case CF_DEVPREDICT:
1128                         case CF_DEVSEASONAL:
1129                         case CF_SEASONAL:
1130                         case CF_AVERAGE:
1131                             validsteps++;
1132                             printval += im->gdes[vidx].data[ii];
1133                             break;
1134                         case CF_MINIMUM:
1135                             printval = min( printval, im->gdes[vidx].data[ii]);
1136                             break;
1137                         case CF_FAILURES:
1138                         case CF_MAXIMUM:
1139                             printval = max( printval, im->gdes[vidx].data[ii]);
1140                             break;
1141                         case CF_LAST:
1142                             printval = im->gdes[vidx].data[ii];
1143                     }
1144                 }
1145                 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1146                     if (validsteps > 1) {
1147                         printval = (printval / validsteps);
1148                     }
1149                 }
1150             } /* prepare printval */
1151
1152             if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1153                 if (im->gdes[i].gf == GF_PRINT){
1154                     (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1155                     sprintf((*prdata)[prlines-2],"%s (%lu)",
1156                                         ctime(&printtime),printtime);
1157                     (*prdata)[prlines-1] = NULL;
1158                 } else {
1159                     sprintf(im->gdes[i].legend,"%s (%lu)",
1160                                         ctime(&printtime),printtime);
1161                     graphelement = 1;
1162                 }
1163             } else {
1164             if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1165                 /* Magfact is set to -1 upon entry to print_calc.  If it
1166                  * is still less than 0, then we need to run auto_scale.
1167                  * Otherwise, put the value into the correct units.  If
1168                  * the value is 0, then do not set the symbol or magnification
1169                  * so next the calculation will be performed again. */
1170                 if (magfact < 0.0) {
1171                     auto_scale(im,&printval,&si_symb,&magfact);
1172                     if (printval == 0.0)
1173                         magfact = -1.0;
1174                 } else {
1175                     printval /= magfact;
1176                 }
1177                 *(++percent_s) = 's';
1178             } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1179                 auto_scale(im,&printval,&si_symb,&magfact);
1180             }
1181
1182             if (im->gdes[i].gf == GF_PRINT){
1183                 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1184                 if (bad_format(im->gdes[i].format)) {
1185                         rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1186                         return -1;
1187                 }
1188 #ifdef HAVE_SNPRINTF
1189                 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1190 #else
1191                 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1192 #endif
1193                 (*prdata)[prlines-1] = NULL;
1194             } else {
1195                 /* GF_GPRINT */
1196
1197                 if (bad_format(im->gdes[i].format)) {
1198                         rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1199                         return -1;
1200                 }
1201 #ifdef HAVE_SNPRINTF
1202                 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1203 #else
1204                 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1205 #endif
1206                 graphelement = 1;
1207             }
1208             }
1209             break;
1210         case GF_COMMENT:
1211         case GF_LINE:
1212         case GF_AREA:
1213         case GF_TICK:
1214         case GF_PART:
1215         case GF_STACK:
1216         case GF_HRULE:
1217         case GF_VRULE:
1218             graphelement = 1;
1219             break;
1220         case GF_DEF:
1221         case GF_CDEF:       
1222         case GF_VDEF:       
1223             break;
1224         }
1225     }
1226     return graphelement;
1227 }
1228
1229
1230 /* place legends with color spots */
1231 int
1232 leg_place(image_desc_t *im)
1233 {
1234     /* graph labels */
1235     int   interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1236     int   box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1237     int   border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1238     int   fill=0, fill_last;
1239     int   leg_c = 0;
1240     int   leg_x = border, leg_y = im->ygif;
1241     int   leg_cc;
1242     int   glue = 0;
1243     int   i,ii, mark = 0;
1244     char  prt_fctn; /*special printfunctions */
1245     int  *legspace;
1246
1247   if( !(im->extra_flags & NOLEGEND) ) {
1248     if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1249        rrd_set_error("malloc for legspace");
1250        return -1;
1251     }
1252
1253     for(i=0;i<im->gdes_c;i++){
1254         fill_last = fill;
1255
1256         leg_cc = strlen(im->gdes[i].legend);
1257         
1258         /* is there a controle code ant the end of the legend string ? */ 
1259         if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1260             prt_fctn = im->gdes[i].legend[leg_cc-1];
1261             leg_cc -= 2;
1262             im->gdes[i].legend[leg_cc] = '\0';
1263         } else {
1264             prt_fctn = '\0';
1265         }
1266         /* remove exess space */
1267         while (prt_fctn=='g' && 
1268                leg_cc > 0 && 
1269                im->gdes[i].legend[leg_cc-1]==' '){
1270            leg_cc--;
1271            im->gdes[i].legend[leg_cc]='\0';
1272         }
1273         if (leg_cc != 0 ){          
1274            legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1275            
1276            if (fill > 0){ 
1277                /* no interleg space if string ends in \g */
1278                fill += legspace[i];
1279             }
1280             if (im->gdes[i].gf != GF_GPRINT && 
1281                 im->gdes[i].gf != GF_COMMENT) { 
1282                 fill += box;       
1283             }
1284            fill += gfx_get_text_width(fill+border,im->text_prop[TEXT_PROP_LEGEND].font,
1285                                       im->text_prop[TEXT_PROP_LEGEND].size,
1286                                       im->tabwidth,
1287                                       im->gdes[i].legend);
1288             leg_c++;
1289         } else {
1290            legspace[i]=0;
1291         }
1292         /* who said there was a special tag ... ?*/
1293         if (prt_fctn=='g') {    
1294            prt_fctn = '\0';
1295         }
1296         if (prt_fctn == '\0') {
1297             if (i == im->gdes_c -1 ) prt_fctn ='l';
1298             
1299             /* is it time to place the legends ? */
1300             if (fill > im->xgif - 2*border){
1301                 if (leg_c > 1) {
1302                     /* go back one */
1303                     i--; 
1304                     fill = fill_last;
1305                     leg_c--;
1306                     prt_fctn = 'j';
1307                 } else {
1308                     prt_fctn = 'l';
1309                 }
1310                 
1311             }
1312         }
1313
1314
1315         if (prt_fctn != '\0'){
1316             leg_x = border;
1317             if (leg_c >= 2 && prt_fctn == 'j') {
1318                 glue = (im->xgif - fill - 2* border) / (leg_c-1);
1319             } else {
1320                 glue = 0;
1321             }
1322             if (prt_fctn =='c') leg_x =  (im->xgif - fill) / 2.0;
1323             if (prt_fctn =='r') leg_x =  im->xgif - fill - border;
1324
1325             for(ii=mark;ii<=i;ii++){
1326                 if(im->gdes[ii].legend[0]=='\0')
1327                     continue;
1328                 im->gdes[ii].leg_x = leg_x;
1329                 im->gdes[ii].leg_y = leg_y;
1330                 leg_x += 
1331                  gfx_get_text_width(leg_x,im->text_prop[TEXT_PROP_LEGEND].font,
1332                                       im->text_prop[TEXT_PROP_LEGEND].size,
1333                                       im->tabwidth,
1334                                       im->gdes[ii].legend) 
1335                    + legspace[ii]
1336                    + glue;
1337                 if (im->gdes[ii].gf != GF_GPRINT && 
1338                     im->gdes[ii].gf != GF_COMMENT) 
1339                     leg_x += box;          
1340             }       
1341             leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1342             if (prt_fctn == 's') leg_y -=  im->text_prop[TEXT_PROP_LEGEND].size*1.2;       
1343             fill = 0;
1344             leg_c = 0;
1345             mark = ii;
1346         }          
1347     }
1348     im->ygif = leg_y+6;
1349     free(legspace);
1350   }
1351   return 0;
1352 }
1353
1354 /* create a grid on the graph. it determines what to do
1355    from the values of xsize, start and end */
1356
1357 /* the xaxis labels are determined from the number of seconds per pixel
1358    in the requested graph */
1359
1360
1361
1362 int
1363 horizontal_grid(gfx_canvas_t *canvas, image_desc_t   *im)
1364 {
1365     double   range;
1366     double   scaledrange;
1367     int      pixel,i;
1368     int      sgrid,egrid;
1369     double   gridstep;
1370     double   scaledstep;
1371     char     graph_label[100];
1372     double   x0,x1,y0,y1;
1373     int      labfact,gridind;
1374     int      decimals, fractionals;
1375     char     labfmt[64];
1376
1377     labfact=2;
1378     gridind=-1;
1379     range =  im->maxval - im->minval;
1380     scaledrange = range / im->magfact;
1381
1382         /* does the scale of this graph make it impossible to put lines
1383            on it? If so, give up. */
1384         if (isnan(scaledrange)) {
1385                 return 0;
1386         }
1387
1388     /* find grid spaceing */
1389     pixel=1;
1390     if(isnan(im->ygridstep)){
1391         if(im->extra_flags & ALTYGRID) {
1392             /* find the value with max number of digits. Get number of digits */
1393             decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1394             if(decimals <= 0) /* everything is small. make place for zero */
1395                 decimals = 1;
1396             
1397             fractionals = floor(log10(range));
1398             if(fractionals < 0) /* small amplitude. */
1399                 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1400             else
1401                 sprintf(labfmt, "%%%d.1f", decimals + 1);
1402             gridstep = pow((double)10, (double)fractionals);
1403             if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1404                 gridstep = 0.1;
1405             /* should have at least 5 lines but no more then 15 */
1406             if(range/gridstep < 5)
1407                 gridstep /= 10;
1408             if(range/gridstep > 15)
1409                 gridstep *= 10;
1410             if(range/gridstep > 5) {
1411                 labfact = 1;
1412                 if(range/gridstep > 8)
1413                     labfact = 2;
1414             }
1415             else {
1416                 gridstep /= 5;
1417                 labfact = 5;
1418             }
1419         }
1420         else {
1421             for(i=0;ylab[i].grid > 0;i++){
1422                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1423                 if (gridind == -1 && pixel > 5) {
1424                     gridind = i;
1425                     break;
1426                 }
1427             }
1428             
1429             for(i=0; i<4;i++) {
1430                if (pixel * ylab[gridind].lfac[i] >=  2 * im->text_prop[TEXT_PROP_AXIS].size) {
1431                   labfact =  ylab[gridind].lfac[i];
1432                   break;
1433                }                          
1434             } 
1435             
1436             gridstep = ylab[gridind].grid * im->magfact;
1437         }
1438     } else {
1439         gridstep = im->ygridstep;
1440         labfact = im->ylabfact;
1441     }
1442     
1443    x0=im->xorigin;
1444    x1=im->xorigin+im->xsize;
1445    
1446     sgrid = (int)( im->minval / gridstep - 1);
1447     egrid = (int)( im->maxval / gridstep + 1);
1448     scaledstep = gridstep/im->magfact;
1449     for (i = sgrid; i <= egrid; i++){
1450        y0=ytr(im,gridstep*i);
1451        if ( y0 >= im->yorigin-im->ysize
1452                  && y0 <= im->yorigin){       
1453             if(i % labfact == 0){               
1454                 if (i==0 || im->symbol == ' ') {
1455                     if(scaledstep < 1){
1456                         if(im->extra_flags & ALTYGRID) {
1457                             sprintf(graph_label,labfmt,scaledstep*i);
1458                         }
1459                         else {
1460                             sprintf(graph_label,"%4.1f",scaledstep*i);
1461                         }
1462                     } else {
1463                         sprintf(graph_label,"%4.0f",scaledstep*i);
1464                     }
1465                 }else {
1466                     if(scaledstep < 1){
1467                         sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1468                     } else {
1469                         sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1470                     }
1471                 }
1472
1473                gfx_new_text ( canvas,
1474                               x0-im->text_prop[TEXT_PROP_AXIS].size/1.5, y0,
1475                               im->graph_col[GRC_FONT],
1476                               im->text_prop[TEXT_PROP_AXIS].font,
1477                               im->text_prop[TEXT_PROP_AXIS].size,
1478                               im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1479                               graph_label );
1480                gfx_new_line ( canvas,
1481                               x0-2,y0,
1482                               x1+2,y0,
1483                               MGRIDWIDTH, im->graph_col[GRC_MGRID] );          
1484                
1485             } else {            
1486                gfx_new_line ( canvas,
1487                               x0-1,y0,
1488                               x1+1,y0,
1489                               GRIDWIDTH, im->graph_col[GRC_GRID] );            
1490                
1491             }       
1492         }       
1493     } 
1494     return 1;
1495 }
1496
1497 /* logaritmic horizontal grid */
1498 int
1499 horizontal_log_grid(gfx_canvas_t *canvas, image_desc_t   *im)   
1500 {
1501     double   pixpex;
1502     int      ii,i;
1503     int      minoridx=0, majoridx=0;
1504     char     graph_label[100];
1505     double   x0,x1,y0,y1;   
1506     double   value, pixperstep, minstep;
1507
1508     /* find grid spaceing */
1509     pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1510
1511         if (isnan(pixpex)) {
1512                 return 0;
1513         }
1514
1515     for(i=0;yloglab[i][0] > 0;i++){
1516         minstep = log10(yloglab[i][0]);
1517         for(ii=1;yloglab[i][ii+1] > 0;ii++){
1518             if(yloglab[i][ii+2]==0){
1519                 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1520                 break;
1521             }
1522         }
1523         pixperstep = pixpex * minstep;
1524         if(pixperstep > 5){minoridx = i;}
1525        if(pixperstep > 2 *  im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1526     }
1527    
1528    x0=im->xorigin;
1529    x1=im->xorigin+im->xsize;
1530     /* paint minor grid */
1531     for (value = pow((double)10, log10(im->minval) 
1532                           - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1533          value  <= im->maxval;
1534          value *= yloglab[minoridx][0]){
1535         if (value < im->minval) continue;
1536         i=0;    
1537         while(yloglab[minoridx][++i] > 0){          
1538            y0 = ytr(im,value * yloglab[minoridx][i]);
1539            if (y0 <= im->yorigin - im->ysize) break;
1540            gfx_new_line ( canvas,
1541                           x0-1,y0,
1542                           x1+1,y0,
1543                           GRIDWIDTH, im->graph_col[GRC_GRID] );
1544         }
1545     }
1546
1547     /* paint major grid and labels*/
1548     for (value = pow((double)10, log10(im->minval) 
1549                           - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1550          value <= im->maxval;
1551          value *= yloglab[majoridx][0]){
1552         if (value < im->minval) continue;
1553         i=0;    
1554         while(yloglab[majoridx][++i] > 0){          
1555            y0 = ytr(im,value * yloglab[majoridx][i]);    
1556            if (y0 <= im->yorigin - im->ysize) break;
1557            gfx_new_line ( canvas,
1558                           x0-2,y0,
1559                           x1+2,y0,
1560                           MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1561            
1562            sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1563            gfx_new_text ( canvas,
1564                           x0-im->text_prop[TEXT_PROP_AXIS].size/1.5, y0,
1565                           im->graph_col[GRC_FONT],
1566                           im->text_prop[TEXT_PROP_AXIS].font,
1567                           im->text_prop[TEXT_PROP_AXIS].size,
1568                           im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1569                           graph_label );
1570         } 
1571     }
1572         return 1;
1573 }
1574
1575
1576 void
1577 vertical_grid(
1578     gfx_canvas_t   *canvas,
1579     image_desc_t   *im )
1580 {   
1581     int xlab_sel;               /* which sort of label and grid ? */
1582     time_t ti, tilab;
1583     long factor;
1584     char graph_label[100];
1585     double x0,y0,y1; /* points for filled graph and more*/
1586    
1587
1588     /* the type of time grid is determined by finding
1589        the number of seconds per pixel in the graph */
1590     
1591     
1592     if(im->xlab_user.minsec == -1){
1593         factor=(im->end - im->start)/im->xsize;
1594         xlab_sel=0;
1595         while ( xlab[xlab_sel+1].minsec != -1 
1596                 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1597         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1598         im->xlab_user.gridst = xlab[xlab_sel].gridst;
1599         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1600         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1601         im->xlab_user.labtm = xlab[xlab_sel].labtm;
1602         im->xlab_user.labst = xlab[xlab_sel].labst;
1603         im->xlab_user.precis = xlab[xlab_sel].precis;
1604         im->xlab_user.stst = xlab[xlab_sel].stst;
1605     }
1606     
1607     /* y coords are the same for every line ... */
1608     y0 = im->yorigin;
1609     y1 = im->yorigin-im->ysize;
1610    
1611
1612     /* paint the minor grid */
1613     for(ti = find_first_time(im->start,
1614                             im->xlab_user.gridtm,
1615                             im->xlab_user.gridst);
1616         ti < im->end; 
1617         ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1618         ){
1619         /* are we inside the graph ? */
1620         if (ti < im->start || ti > im->end) continue;
1621        x0 = xtr(im,ti);       
1622        gfx_new_line(canvas,x0,y0+1, x0,y1-1,GRIDWIDTH, im->graph_col[GRC_GRID]);
1623        
1624     }
1625
1626     /* paint the major grid */
1627     for(ti = find_first_time(im->start,
1628                             im->xlab_user.mgridtm,
1629                             im->xlab_user.mgridst);
1630         ti < im->end; 
1631         ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1632         ){
1633         /* are we inside the graph ? */
1634         if (ti < im->start || ti > im->end) continue;
1635        x0 = xtr(im,ti);
1636        gfx_new_line(canvas,x0,y0+2, x0,y1-2,MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1637        
1638     }
1639     /* paint the labels below the graph */
1640     for(ti = find_first_time(im->start,
1641                             im->xlab_user.labtm,
1642                             im->xlab_user.labst);
1643         ti <= im->end; 
1644         ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1645         ){
1646         tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1647
1648 #if HAVE_STRFTIME
1649         strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1650 #else
1651 # error "your libc has no strftime I guess we'll abort the exercise here."
1652 #endif
1653        gfx_new_text ( canvas,
1654                       xtr(im,tilab), y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1655                       im->graph_col[GRC_FONT],
1656                       im->text_prop[TEXT_PROP_AXIS].font,
1657                       im->text_prop[TEXT_PROP_AXIS].size,
1658                       im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1659                       graph_label );
1660        
1661     }
1662
1663 }
1664
1665
1666 void 
1667 axis_paint(
1668    image_desc_t   *im,
1669    gfx_canvas_t   *canvas
1670            )
1671 {   
1672     /* draw x and y axis */
1673     gfx_new_line ( canvas, im->xorigin+im->xsize,im->yorigin,
1674                       im->xorigin+im->xsize,im->yorigin-im->ysize,
1675                       GRIDWIDTH, im->graph_col[GRC_GRID]);
1676        
1677        gfx_new_line ( canvas, im->xorigin,im->yorigin-im->ysize,
1678                          im->xorigin+im->xsize,im->yorigin-im->ysize,
1679                          GRIDWIDTH, im->graph_col[GRC_GRID]);
1680    
1681        gfx_new_line ( canvas, im->xorigin-4,im->yorigin,
1682                          im->xorigin+im->xsize+4,im->yorigin,
1683                          MGRIDWIDTH, im->graph_col[GRC_GRID]);
1684    
1685        gfx_new_line ( canvas, im->xorigin,im->yorigin+4,
1686                          im->xorigin,im->yorigin-im->ysize-4,
1687                          MGRIDWIDTH, im->graph_col[GRC_GRID]);
1688    
1689     
1690     /* arrow for X axis direction */
1691     gfx_new_area ( canvas, 
1692                    im->xorigin+im->xsize+4,  im->yorigin-3,
1693                    im->xorigin+im->xsize+4,  im->yorigin+3,
1694                    im->xorigin+im->xsize+9,  im->yorigin,  
1695                    im->graph_col[GRC_ARROW]);
1696    
1697    
1698    
1699 }
1700
1701 void
1702 grid_paint(
1703     image_desc_t   *im,
1704     gfx_canvas_t   *canvas
1705            
1706     )
1707 {   
1708     long i;
1709     int boxH=8, boxV=8;
1710     int res=0;
1711     double x0,x1,x2,x3,y0,y1,y2,y3; /* points for filled graph and more*/
1712     gfx_node_t *node;
1713     
1714
1715     /* draw 3d border */
1716     node = gfx_new_area (canvas, 0,im->ygif,
1717                                  2,im->ygif-2,
1718                                  2,2,im->graph_col[GRC_SHADEA]);
1719     gfx_add_point( node , im->xgif - 2, 2 );
1720     gfx_add_point( node , im->xgif, 0 );
1721     gfx_add_point( node , 0,0 );
1722 /*    gfx_add_point( node , 0,im->ygif ); */
1723    
1724     node =  gfx_new_area (canvas, 2,im->ygif-2,
1725                                   im->xgif-2,im->ygif-2,
1726                                   im->xgif - 2, 2,
1727                                  im->graph_col[GRC_SHADEB]);
1728     gfx_add_point( node ,   im->xgif,0);
1729     gfx_add_point( node ,   im->xgif,im->ygif);
1730     gfx_add_point( node ,   0,im->ygif);
1731 /*    gfx_add_point( node , 0,im->ygif ); */
1732    
1733    
1734     if (im->draw_x_grid == 1 )
1735       vertical_grid(canvas, im);
1736     
1737     if (im->draw_y_grid == 1){
1738         if(im->logarithmic){
1739                 res = horizontal_log_grid(canvas,im);
1740         } else {
1741                 res = horizontal_grid(canvas,im);
1742         }
1743
1744         /* dont draw horizontal grid if there is no min and max val */
1745         if (! res ) {
1746           char *nodata = "No Data found";
1747            gfx_new_text(canvas,im->xgif/2, (2*im->yorigin-im->ysize) / 2,
1748                         im->graph_col[GRC_FONT],
1749                         im->text_prop[TEXT_PROP_AXIS].font,
1750                         im->text_prop[TEXT_PROP_AXIS].size,
1751                         im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1752                         nodata );          
1753         }
1754     }
1755
1756     /* yaxis description */
1757     gfx_new_text( canvas,
1758                   7, (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, im->tabwidth, 270.0,
1762                   GFX_H_CENTER, GFX_V_CENTER,
1763                   im->ylegend);
1764    
1765     /* graph title */
1766     gfx_new_text( canvas,
1767                   im->xgif/2, im->text_prop[TEXT_PROP_TITLE].size*1.5,
1768                   im->graph_col[GRC_FONT],
1769                   im->text_prop[TEXT_PROP_TITLE].font,
1770                   im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1771                   GFX_H_CENTER, GFX_V_CENTER,
1772                   im->title);
1773
1774    /* graph labels */
1775    if( !(im->extra_flags & NOLEGEND) ) {
1776       for(i=0;i<im->gdes_c;i++){
1777          if(im->gdes[i].legend[0] =='\0')
1778            continue;
1779          
1780          if(im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT){
1781             x0 = im->gdes[i].leg_x;
1782             y0 = im->gdes[i].leg_y+1.0;
1783             x1 = x0;
1784             x2 = x0+boxH;
1785             x3 = x0+boxH;
1786             y1 = y0+boxV;
1787             y2 = y0+boxV;
1788             y3 = y0;
1789             node = gfx_new_area(canvas, x0,y0,x1,y1,x2,y2 ,im->gdes[i].col);
1790             gfx_add_point ( node, x3, y3 );
1791 /*          gfx_add_point ( node, x0, y0 ); */
1792             node = gfx_new_line(canvas, x0,y0,x1,y1 ,GRIDWIDTH, im->graph_col[GRC_FRAME]);
1793             gfx_add_point ( node, x2, y2 );
1794             gfx_add_point ( node, x3, y3 );
1795             gfx_add_point ( node, x0, y0 );
1796             
1797             gfx_new_text ( canvas, x0+boxH+6,  (y0+y2) / 2.0,
1798                            im->graph_col[GRC_FONT],
1799                            im->text_prop[TEXT_PROP_AXIS].font,
1800                            im->text_prop[TEXT_PROP_AXIS].size,
1801                            im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
1802                            im->gdes[i].legend );
1803             
1804          } else {
1805             x0 = im->gdes[i].leg_x;
1806             y0 = im->gdes[i].leg_y;
1807                 
1808             gfx_new_text ( canvas, x0,  (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_BOTTOM,
1813                            im->gdes[i].legend );
1814             
1815          }
1816       }
1817    }
1818 }
1819
1820
1821 /*****************************************************
1822  * lazy check make sure we rely need to create this graph
1823  *****************************************************/
1824
1825 int lazy_check(image_desc_t *im){
1826     FILE *fd = NULL;
1827         int size = 1;
1828     struct stat  gifstat;
1829     
1830     if (im->lazy == 0) return 0; /* no lazy option */
1831     if (stat(im->graphfile,&gifstat) != 0) 
1832       return 0; /* can't stat */
1833     /* one pixel in the existing graph is more then what we would
1834        change here ... */
1835     if (time(NULL) - gifstat.st_mtime > 
1836         (im->end - im->start) / im->xsize) 
1837       return 0;
1838     if ((fd = fopen(im->graphfile,"rb")) == NULL) 
1839       return 0; /* the file does not exist */
1840     switch (im->imgformat) {
1841     case IF_GIF:
1842            size = GifSize(fd,&(im->xgif),&(im->ygif));
1843            break;
1844     case IF_PNG:
1845            size = PngSize(fd,&(im->xgif),&(im->ygif));
1846            break;
1847     }
1848     fclose(fd);
1849     return size;
1850 }
1851
1852 void
1853 pie_part(gfx_canvas_t *canvas, gfx_color_t color,
1854             double PieCenterX, double PieCenterY, double Radius,
1855             double startangle, double endangle)
1856 {
1857     gfx_node_t *node;
1858     double angle;
1859     double step=M_PI/50; /* Number of iterations for the circle;
1860                          ** 10 is definitely too low, more than
1861                          ** 50 seems to be overkill
1862                          */
1863
1864     /* Strange but true: we have to work clockwise or else
1865     ** anti aliasing nor transparency don't work.
1866     **
1867     ** This test is here to make sure we do it right, also
1868     ** this makes the for...next loop more easy to implement.
1869     ** The return will occur if the user enters a negative number
1870     ** (which shouldn't be done according to the specs) or if the
1871     ** programmers do something wrong (which, as we all know, never
1872     ** happens anyway :)
1873     */
1874     if (endangle<startangle) return;
1875
1876     /* Hidden feature: Radius decreases each full circle */
1877     angle=startangle;
1878     while (angle>=2*M_PI) {
1879         angle  -= 2*M_PI;
1880         Radius *= 0.8;
1881     }
1882
1883     node=gfx_new_area(canvas,
1884                 PieCenterX+sin(startangle)*Radius,
1885                 PieCenterY-cos(startangle)*Radius,
1886                 PieCenterX,
1887                 PieCenterY,
1888                 PieCenterX+sin(endangle)*Radius,
1889                 PieCenterY-cos(endangle)*Radius,
1890                 color);
1891     for (angle=endangle;angle-startangle>=step;angle-=step) {
1892         gfx_add_point(node,
1893                 PieCenterX+sin(angle)*Radius,
1894                 PieCenterY-cos(angle)*Radius );
1895     }
1896 }
1897
1898 /* draw that picture thing ... */
1899 int
1900 graph_paint(image_desc_t *im, char ***calcpr)
1901 {
1902   int i,ii;
1903   int lazy =     lazy_check(im);
1904   int piechart = 0;
1905   double PieStart=0.0, PieSize=0.0, PieCenterX=0.0, PieCenterY=0.0;
1906   FILE  *fo;
1907   gfx_canvas_t *canvas;
1908   gfx_node_t *node;
1909   
1910   double areazero = 0.0;
1911   enum gf_en stack_gf = GF_PRINT;
1912   graph_desc_t *lastgdes = NULL;    
1913   
1914   /* if we are lazy and there is nothing to PRINT ... quit now */
1915   if (lazy && im->prt_c==0) return 0;
1916   
1917   /* pull the data from the rrd files ... */
1918   
1919   if(data_fetch(im)==-1)
1920     return -1;
1921   
1922   /* evaluate VDEF and CDEF operations ... */
1923   if(data_calc(im)==-1)
1924     return -1;
1925   
1926   /* calculate and PRINT and GPRINT definitions. We have to do it at
1927    * this point because it will affect the length of the legends
1928    * if there are no graph elements we stop here ... 
1929    * if we are lazy, try to quit ... 
1930    */
1931   i=print_calc(im,calcpr);
1932   if(i<0) return -1;
1933   if(i==0 || lazy) return 0;
1934   
1935   /* get actual drawing data and find min and max values*/
1936   if(data_proc(im)==-1)
1937     return -1;
1938   
1939   if(!im->logarithmic){si_unit(im);}        /* identify si magnitude Kilo, Mega Giga ? */
1940   
1941   if(!im->rigid && ! im->logarithmic)
1942     expand_range(im);   /* make sure the upper and lower limit are
1943                            sensible values */
1944   
1945   /* init xtr and ytr */
1946   /* determine the actual size of the gif to draw. The size given
1947      on the cmdline is the graph area. But we need more as we have
1948      draw labels and other things outside the graph area */
1949   
1950   
1951   im->xorigin = 10 + 9 *  im->text_prop[TEXT_PROP_LEGEND].size;
1952
1953   xtr(im,0); 
1954   
1955   im->yorigin = 10 + im->ysize;
1956
1957   ytr(im,DNAN);
1958   
1959   if(im->title[0] != '\0')
1960     im->yorigin += im->text_prop[TEXT_PROP_TITLE].size*3+4;
1961   
1962   im->xgif= 20 +im->xsize + im->xorigin;
1963   im->ygif= im->yorigin+2* im->text_prop[TEXT_PROP_LEGEND].size;
1964
1965   /* check if we need to draw a piechart */
1966   for(i=0;i<im->gdes_c;i++){
1967     if (im->gdes[i].gf == GF_PART) {
1968       piechart=1;
1969       break;
1970     }
1971   }
1972
1973   if (piechart) {
1974         /* allocate enough space for the piechart itself (PieSize), 20%
1975         ** more for the background and an additional 50 pixels spacing.
1976         */
1977     if (im->xsize < im->ysize)
1978         PieSize = im->xsize;
1979     else
1980         PieSize = im->ysize;
1981     im->xgif += PieSize*1.2 + 50;
1982
1983     PieCenterX = im->xorigin + im->xsize + 50 + PieSize*0.6;
1984     PieCenterY = im->yorigin - PieSize*0.5;
1985   }
1986
1987   /* determine where to place the legends onto the graphics.
1988      and set im->ygif to match space requirements for text */
1989   if(leg_place(im)==-1)
1990     return -1;
1991
1992   canvas=gfx_new_canvas();
1993
1994
1995   /* the actual graph is created by going through the individual
1996      graph elements and then drawing them */
1997   
1998   node=gfx_new_area ( canvas,
1999                       0, 0,
2000                       im->xgif, 0,
2001                       im->xgif, im->ygif,
2002                       im->graph_col[GRC_BACK]);
2003
2004   gfx_add_point(node,0, im->ygif);
2005
2006   node=gfx_new_area ( canvas,
2007                       im->xorigin,             im->yorigin, 
2008                       im->xorigin + im->xsize, im->yorigin,
2009                       im->xorigin + im->xsize, im->yorigin-im->ysize,
2010                       im->graph_col[GRC_CANVAS]);
2011   
2012   gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2013
2014 #if 0
2015 /******************************************************************
2016  ** Just to play around.  If you see this, I forgot to remove it **
2017  ******************************************************************/
2018   im->ygif+=100;
2019   node=gfx_new_area(canvas,
2020                         0,              im->ygif-100,
2021                         im->xgif,       im->ygif-100,
2022                         im->xgif,       im->ygif,
2023                         im->graph_col[GRC_CANVAS]);
2024   gfx_add_point(node,0,im->ygif);
2025
2026   /* Four areas:
2027   ** top left:     current way, solid color
2028   ** top right:    proper way,  solid color
2029   ** bottom left:  current way, alpha=0x80, partially overlapping
2030   ** bottom right: proper way,  alpha=0x80, partially overlapping
2031   */
2032   {
2033     double x,y,x1,y1,x2,y2,x3,y3,x4,y4;
2034
2035     x=(im->xgif-40)/6;
2036     y=     (100-40)/6;
2037     x1=    20;   y1=im->ygif-100+20;
2038     x2=3*x+20;   y2=im->ygif-100+20;
2039     x3=  x+20;   y3=im->ygif-100+20+2*y;
2040     x4=4*x+20;   y4=im->ygif-100+20+2*y;
2041
2042     node=gfx_new_area(canvas,
2043                         x1,y1,
2044                         x1+3*x,y1,
2045                         x1+3*x,y1+3*y,
2046                         0xFF0000FF);
2047     gfx_add_point(node,x1,y1+3*y);
2048     node=gfx_new_area(canvas,
2049                         x2,y2,
2050                         x2,y2+3*y,
2051                         x2+3*x,y2+3*y,
2052                         0xFFFF00FF);
2053     gfx_add_point(node,x2+3*x,y2);
2054     node=gfx_new_area(canvas,
2055                         x3,y3,
2056                         x3+2*x,y3,
2057                         x3+2*x,y3+3*y,
2058                         0x00FF007F);
2059     gfx_add_point(node,x3,y3+3*y);
2060     node=gfx_new_area(canvas,
2061                         x4,y4,
2062                         x4,y4+3*y,
2063                         x4+2*x,y4+3*y,
2064                         0x0000FF7F);
2065     gfx_add_point(node,x4+2*x,y4);
2066   }
2067                         
2068 #endif
2069
2070   if (piechart) {
2071     pie_part(canvas,im->graph_col[GRC_CANVAS],PieCenterX,PieCenterY,PieSize*0.6,0,2*M_PI);
2072   }
2073
2074   if (im->minval > 0.0)
2075     areazero = im->minval;
2076   if (im->maxval < 0.0)
2077     areazero = im->maxval;
2078   
2079   axis_paint(im,canvas);
2080
2081
2082   for(i=0;i<im->gdes_c;i++){
2083     switch(im->gdes[i].gf){
2084     case GF_CDEF:
2085     case GF_VDEF:
2086     case GF_DEF:
2087     case GF_PRINT:
2088     case GF_GPRINT:
2089     case GF_COMMENT:
2090     case GF_HRULE:
2091     case GF_VRULE:
2092       break;
2093     case GF_TICK:
2094       for (ii = 0; ii < im->xsize; ii++)
2095         {
2096           if (!isnan(im->gdes[i].p_data[ii]) && 
2097               im->gdes[i].p_data[ii] > 0.0)
2098             { 
2099               /* generate a tick */
2100               gfx_new_line(canvas, im -> xorigin + ii, 
2101                            im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2102                            im -> xorigin + ii, 
2103                            im -> yorigin,
2104                            1.0,
2105                            im -> gdes[i].col );
2106             }
2107         }
2108       break;
2109     case GF_LINE:
2110     case GF_AREA:
2111       stack_gf = im->gdes[i].gf;
2112     case GF_STACK:          
2113       /* fix data points at oo and -oo */
2114       for(ii=0;ii<im->xsize;ii++){
2115         if (isinf(im->gdes[i].p_data[ii])){
2116           if (im->gdes[i].p_data[ii] > 0) {
2117             im->gdes[i].p_data[ii] = im->maxval ;
2118           } else {
2119             im->gdes[i].p_data[ii] = im->minval ;
2120           }                 
2121           
2122         }
2123       } /* for */
2124       
2125       if (im->gdes[i].col != 0x0){               
2126         /* GF_LINE and friend */
2127         if(stack_gf == GF_LINE ){
2128           node = NULL;
2129           for(ii=1;ii<im->xsize;ii++){
2130             if ( ! isnan(im->gdes[i].p_data[ii-1])
2131                  && ! isnan(im->gdes[i].p_data[ii])){
2132               if (node == NULL){
2133                 node = gfx_new_line(canvas,
2134                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2135                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2136                                     im->gdes[i].linewidth,
2137                                     im->gdes[i].col);
2138               } else {
2139                 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2140               }
2141             } else {
2142               node = NULL;
2143             }
2144           }
2145         } else {
2146           int area_start=-1;
2147           node = NULL;
2148           for(ii=1;ii<im->xsize;ii++){
2149             /* open an area */
2150             if ( ! isnan(im->gdes[i].p_data[ii-1])
2151                  && ! isnan(im->gdes[i].p_data[ii])){
2152               if (node == NULL){
2153                 float ybase = 0.0;
2154                 if (im->gdes[i].gf == GF_STACK) {
2155                   ybase = ytr(im,lastgdes->p_data[ii-1]);
2156                 } else {
2157                   ybase =  ytr(im,areazero);
2158                 }
2159                 area_start = ii-1;
2160                 node = gfx_new_area(canvas,
2161                                     ii-1+im->xorigin,ybase,
2162                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2163                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2164                                     im->gdes[i].col
2165                                     );
2166               } else {
2167                 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2168               }
2169             }
2170
2171             if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2172               /* GF_AREA STACK type*/
2173               if (im->gdes[i].gf == GF_STACK ) {
2174                 int iii;
2175                 for (iii=ii-1;iii>area_start;iii--){
2176                   gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2177                 }
2178               } else {
2179                 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2180               };
2181               node=NULL;
2182             };
2183           }             
2184         } /* else GF_LINE */
2185       } /* if color != 0x0 */
2186       /* make sure we do not run into trouble when stacking on NaN */
2187       for(ii=0;ii<im->xsize;ii++){
2188         if (isnan(im->gdes[i].p_data[ii])) {
2189           double ybase = 0.0;
2190           if (lastgdes) {
2191             ybase = ytr(im,lastgdes->p_data[ii-1]);
2192           };
2193           if (isnan(ybase) || !lastgdes ){
2194             ybase =  ytr(im,areazero);
2195           }
2196           im->gdes[i].p_data[ii] = ybase;
2197         }
2198       } 
2199       lastgdes = &(im->gdes[i]);                         
2200       break;
2201     case GF_PART:
2202       if(isnan(im->gdes[i].yrule)) /* fetch variable */
2203         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2204      
2205       if (finite(im->gdes[i].yrule)) {  /* even the fetched var can be NaN */
2206         pie_part(canvas,im->gdes[i].col,
2207                 PieCenterX,PieCenterY,PieSize/2,
2208                 M_PI*2.0*PieStart/100.0,
2209                 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2210         PieStart += im->gdes[i].yrule;
2211       }
2212       break;
2213     } /* switch */
2214   }
2215   grid_paint(im,canvas);
2216   
2217   /* the RULES are the last thing to paint ... */
2218   for(i=0;i<im->gdes_c;i++){    
2219     
2220     switch(im->gdes[i].gf){
2221     case GF_HRULE:
2222       if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2223         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2224       };
2225       if(im->gdes[i].yrule >= im->minval
2226          && im->gdes[i].yrule <= im->maxval)
2227         gfx_new_line(canvas,
2228                      im->xorigin,ytr(im,im->gdes[i].yrule),
2229                      im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2230                      1.0,im->gdes[i].col); 
2231       break;
2232     case GF_VRULE:
2233       if(im->gdes[i].xrule == 0) { /* fetch variable */
2234         im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2235       };
2236       if(im->gdes[i].xrule >= im->start
2237          && im->gdes[i].xrule <= im->end)
2238         gfx_new_line(canvas,
2239                      xtr(im,im->gdes[i].xrule),im->yorigin,
2240                      xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2241                      1.0,im->gdes[i].col); 
2242       break;
2243     default:
2244       break;
2245     }
2246   }
2247
2248   
2249   if (strcmp(im->graphfile,"-")==0) {
2250 #ifdef WIN32
2251     /* Change translation mode for stdout to BINARY */
2252     _setmode( _fileno( stdout ), O_BINARY );
2253 #endif
2254     fo = stdout;
2255   } else {
2256     if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2257       rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2258                     strerror(errno));
2259       return (-1);
2260     }
2261   }
2262   switch (im->imgformat) {
2263   case IF_GIF:
2264     break;
2265   case IF_PNG:
2266     gfx_render_png (canvas,im->xgif,im->ygif,im->zoom,0x0,fo);
2267     break;
2268   }
2269   if (strcmp(im->graphfile,"-") != 0)
2270     fclose(fo);
2271    
2272   gfx_destroy(canvas);
2273   return 0;
2274 }
2275
2276
2277 /*****************************************************
2278  * graph stuff 
2279  *****************************************************/
2280
2281 int
2282 gdes_alloc(image_desc_t *im){
2283
2284     long def_step = (im->end-im->start)/im->xsize;
2285     
2286     if (im->step > def_step) /* step can be increassed ... no decreassed */
2287       def_step = im->step;
2288
2289     im->gdes_c++;
2290     
2291     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2292                                            * sizeof(graph_desc_t)))==NULL){
2293         rrd_set_error("realloc graph_descs");
2294         return -1;
2295     }
2296
2297
2298     im->gdes[im->gdes_c-1].step=def_step; 
2299     im->gdes[im->gdes_c-1].start=im->start; 
2300     im->gdes[im->gdes_c-1].end=im->end; 
2301     im->gdes[im->gdes_c-1].vname[0]='\0'; 
2302     im->gdes[im->gdes_c-1].data=NULL;
2303     im->gdes[im->gdes_c-1].ds_namv=NULL;
2304     im->gdes[im->gdes_c-1].data_first=0;
2305     im->gdes[im->gdes_c-1].p_data=NULL;
2306     im->gdes[im->gdes_c-1].rpnp=NULL;
2307     im->gdes[im->gdes_c-1].col = 0x0;
2308     im->gdes[im->gdes_c-1].legend[0]='\0';
2309     im->gdes[im->gdes_c-1].rrd[0]='\0';
2310     im->gdes[im->gdes_c-1].ds=-1;    
2311     im->gdes[im->gdes_c-1].p_data=NULL;    
2312     return 0;
2313 }
2314
2315 /* copies input untill the first unescaped colon is found
2316    or until input ends. backslashes have to be escaped as well */
2317 int
2318 scan_for_col(char *input, int len, char *output)
2319 {
2320     int inp,outp=0;
2321     for (inp=0; 
2322          inp < len &&
2323            input[inp] != ':' &&
2324            input[inp] != '\0';
2325          inp++){
2326       if (input[inp] == '\\' &&
2327           input[inp+1] != '\0' && 
2328           (input[inp+1] == '\\' ||
2329            input[inp+1] == ':')){
2330         output[outp++] = input[++inp];
2331       }
2332       else {
2333         output[outp++] = input[inp];
2334       }
2335     }
2336     output[outp] = '\0';
2337     return inp;
2338 }
2339
2340 /* Some surgery done on this function, it became ridiculously big.
2341 ** Things moved:
2342 ** - initializing     now in rrd_graph_init()
2343 ** - options parsing  now in rrd_graph_options()
2344 ** - script parsing   now in rrd_graph_script()
2345 */
2346 int 
2347 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2348 {
2349     image_desc_t   im;
2350
2351     rrd_graph_init(&im);
2352
2353     rrd_graph_options(argc,argv,&im);
2354     if (rrd_test_error()) return -1;
2355     
2356     if (strlen(argv[optind])>=MAXPATH) {
2357         rrd_set_error("filename (including path) too long");
2358         return -1;
2359     }
2360     strncpy(im.graphfile,argv[optind],MAXPATH-1);
2361     im.graphfile[MAXPATH-1]='\0';
2362
2363     rrd_graph_script(argc,argv,&im);
2364     if (rrd_test_error()) return -1;
2365
2366     /* Everything is now read and the actual work can start */
2367
2368     (*prdata)=NULL;
2369     if (graph_paint(&im,prdata)==-1){
2370         im_free(&im);
2371         return -1;
2372     }
2373
2374     /* The image is generated and needs to be output.
2375     ** Also, if needed, print a line with information about the image.
2376     */
2377
2378     *xsize=im.xgif;
2379     *ysize=im.ygif;
2380     if (im.imginfo) {
2381         char *filename;
2382         if (!(*prdata)) {
2383             /* maybe prdata is not allocated yet ... lets do it now */
2384             if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2385                 rrd_set_error("malloc imginfo");
2386                 return -1; 
2387             };
2388         }
2389         if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2390          ==NULL){
2391             rrd_set_error("malloc imginfo");
2392             return -1;
2393         }
2394         filename=im.graphfile+strlen(im.graphfile);
2395         while(filename > im.graphfile) {
2396             if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2397             filename--;
2398         }
2399
2400         sprintf((*prdata)[0],im.imginfo,filename,(long)(im.zoom*im.xgif),(long)(im.zoom*im.ygif));
2401     }
2402     im_free(&im);
2403     return 0;
2404 }
2405
2406 void
2407 rrd_graph_init(image_desc_t *im)
2408 {
2409     int i;
2410
2411     im->xlab_user.minsec = -1;
2412     im->xgif=0;
2413     im->ygif=0;
2414     im->xsize = 400;
2415     im->ysize = 100;
2416     im->step = 0;
2417     im->ylegend[0] = '\0';
2418     im->title[0] = '\0';
2419     im->minval = DNAN;
2420     im->maxval = DNAN;    
2421     im->interlaced = 0;
2422     im->unitsexponent= 9999;
2423     im->extra_flags= 0;
2424     im->rigid = 0;
2425     im->imginfo = NULL;
2426     im->lazy = 0;
2427     im->logarithmic = 0;
2428     im->ygridstep = DNAN;
2429     im->draw_x_grid = 1;
2430     im->draw_y_grid = 1;
2431     im->base = 1000;
2432     im->prt_c = 0;
2433     im->gdes_c = 0;
2434     im->gdes = NULL;
2435     im->zoom = 1.0;
2436     im->imgformat = IF_GIF; /* we default to GIF output */
2437
2438     for(i=0;i<DIM(graph_col);i++)
2439         im->graph_col[i]=graph_col[i];
2440
2441     for(i=0;i<DIM(text_prop);i++){        
2442       im->text_prop[i].size = text_prop[i].size;
2443       im->text_prop[i].font = text_prop[i].font;
2444     }
2445 }
2446
2447 void
2448 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2449 {
2450     int                 stroff;    
2451     char                *parsetime_error = NULL;
2452     char                scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2453     time_t              start_tmp=0,end_tmp=0;
2454     long                long_tmp;
2455     struct time_value   start_tv, end_tv;
2456     gfx_color_t         color;
2457
2458     parsetime("end-24h", &start_tv);
2459     parsetime("now", &end_tv);
2460
2461     while (1){
2462         static struct option long_options[] =
2463         {
2464             {"start",      required_argument, 0,  's'},
2465             {"end",        required_argument, 0,  'e'},
2466             {"x-grid",     required_argument, 0,  'x'},
2467             {"y-grid",     required_argument, 0,  'y'},
2468             {"vertical-label",required_argument,0,'v'},
2469             {"width",      required_argument, 0,  'w'},
2470             {"height",     required_argument, 0,  'h'},
2471             {"interlaced", no_argument,       0,  'i'},
2472             {"upper-limit",required_argument, 0,  'u'},
2473             {"lower-limit",required_argument, 0,  'l'},
2474             {"rigid",      no_argument,       0,  'r'},
2475             {"base",       required_argument, 0,  'b'},
2476             {"logarithmic",no_argument,       0,  'o'},
2477             {"color",      required_argument, 0,  'c'},
2478             {"font",       required_argument, 0,  'n'},
2479             {"title",      required_argument, 0,  't'},
2480             {"imginfo",    required_argument, 0,  'f'},
2481             {"imgformat",  required_argument, 0,  'a'},
2482             {"lazy",       no_argument,       0,  'z'},
2483             {"zoom",       required_argument, 0,  'm'},
2484             {"no-legend",  no_argument,       0,  'g'},
2485             {"alt-y-grid", no_argument,       0,   257 },
2486             {"alt-autoscale", no_argument,    0,   258 },
2487             {"alt-autoscale-max", no_argument,    0,   259 },
2488             {"units-exponent",required_argument, 0,  260},
2489             {"step",       required_argument, 0,   261},
2490             {0,0,0,0}};
2491         int option_index = 0;
2492         int opt;
2493
2494
2495         opt = getopt_long(argc, argv, 
2496                           "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2497                           long_options, &option_index);
2498
2499         if (opt == EOF)
2500             break;
2501         
2502         switch(opt) {
2503         case 257:
2504             im->extra_flags |= ALTYGRID;
2505             break;
2506         case 258:
2507             im->extra_flags |= ALTAUTOSCALE;
2508             break;
2509         case 259:
2510             im->extra_flags |= ALTAUTOSCALE_MAX;
2511             break;
2512         case 'g':
2513             im->extra_flags |= NOLEGEND;
2514             break;
2515         case 260:
2516             im->unitsexponent = atoi(optarg);
2517             break;
2518         case 261:
2519             im->step =  atoi(optarg);
2520             break;
2521         case 's':
2522             if ((parsetime_error = parsetime(optarg, &start_tv))) {
2523                 rrd_set_error( "start time: %s", parsetime_error );
2524                 return;
2525             }
2526             break;
2527         case 'e':
2528             if ((parsetime_error = parsetime(optarg, &end_tv))) {
2529                 rrd_set_error( "end time: %s", parsetime_error );
2530                 return;
2531             }
2532             break;
2533         case 'x':
2534             if(strcmp(optarg,"none") == 0){
2535               im->draw_x_grid=0;
2536               break;
2537             };
2538                 
2539             if(sscanf(optarg,
2540                       "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2541                       scan_gtm,
2542                       &im->xlab_user.gridst,
2543                       scan_mtm,
2544                       &im->xlab_user.mgridst,
2545                       scan_ltm,
2546                       &im->xlab_user.labst,
2547                       &im->xlab_user.precis,
2548                       &stroff) == 7 && stroff != 0){
2549                 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2550                 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2551                     rrd_set_error("unknown keyword %s",scan_gtm);
2552                     return;
2553                 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2554                     rrd_set_error("unknown keyword %s",scan_mtm);
2555                     return;
2556                 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2557                     rrd_set_error("unknown keyword %s",scan_ltm);
2558                     return;
2559                 } 
2560                 im->xlab_user.minsec = 1;
2561                 im->xlab_user.stst = im->xlab_form;
2562             } else {
2563                 rrd_set_error("invalid x-grid format");
2564                 return;
2565             }
2566             break;
2567         case 'y':
2568
2569             if(strcmp(optarg,"none") == 0){
2570               im->draw_y_grid=0;
2571               break;
2572             };
2573
2574             if(sscanf(optarg,
2575                       "%lf:%d",
2576                       &im->ygridstep,
2577                       &im->ylabfact) == 2) {
2578                 if(im->ygridstep<=0){
2579                     rrd_set_error("grid step must be > 0");
2580                     return;
2581                 } else if (im->ylabfact < 1){
2582                     rrd_set_error("label factor must be > 0");
2583                     return;
2584                 } 
2585             } else {
2586                 rrd_set_error("invalid y-grid format");
2587                 return;
2588             }
2589             break;
2590         case 'v':
2591             strncpy(im->ylegend,optarg,150);
2592             im->ylegend[150]='\0';
2593             break;
2594         case 'u':
2595             im->maxval = atof(optarg);
2596             break;
2597         case 'l':
2598             im->minval = atof(optarg);
2599             break;
2600         case 'b':
2601             im->base = atol(optarg);
2602             if(im->base != 1024 && im->base != 1000 ){
2603                 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2604                 return;
2605             }
2606             break;
2607         case 'w':
2608             long_tmp = atol(optarg);
2609             if (long_tmp < 10) {
2610                 rrd_set_error("width below 10 pixels");
2611                 return;
2612             }
2613             im->xsize = long_tmp;
2614             break;
2615         case 'h':
2616             long_tmp = atol(optarg);
2617             if (long_tmp < 10) {
2618                 rrd_set_error("height below 10 pixels");
2619                 return;
2620             }
2621             im->ysize = long_tmp;
2622             break;
2623         case 'i':
2624             im->interlaced = 1;
2625             break;
2626         case 'r':
2627             im->rigid = 1;
2628             break;
2629         case 'f':
2630             im->imginfo = optarg;
2631             break;
2632         case 'a':
2633             if((im->imgformat = if_conv(optarg)) == -1) {
2634                 rrd_set_error("unsupported graphics format '%s'",optarg);
2635                 return;
2636             }
2637             break;
2638         case 'z':
2639             im->lazy = 1;
2640             break;
2641         case 'o':
2642             im->logarithmic = 1;
2643             if (isnan(im->minval))
2644                 im->minval=1;
2645             break;
2646         case 'c':
2647             if(sscanf(optarg,
2648                       "%10[A-Z]#%8x",
2649                       col_nam,&color) == 2){
2650                 int ci;
2651                 if((ci=grc_conv(col_nam)) != -1){
2652                     im->graph_col[ci]=color;
2653                 }  else {
2654                   rrd_set_error("invalid color name '%s'",col_nam);
2655                 }
2656             } else {
2657                 rrd_set_error("invalid color def format");
2658                 return -1;
2659             }
2660             break;        
2661         case 'n':{
2662                         /* originally this used char *prop = "" and
2663                         ** char *font = "dummy" however this results
2664                         ** in a SEG fault, at least on RH7.1
2665                         **
2666                         ** The current implementation isn't proper
2667                         ** either, font is never freed and prop uses
2668                         ** a fixed width string
2669                         */
2670             char prop[100];
2671             double size = 1;
2672             char *font;
2673
2674             font=malloc(255);
2675             if(sscanf(optarg,
2676                                 "%10[A-Z]:%lf:%s",
2677                                 prop,&size,font) == 3){
2678                 int sindex;
2679                 if((sindex=text_prop_conv(prop)) != -1){
2680                     im->text_prop[sindex].size=size;              
2681                     im->text_prop[sindex].font=font;
2682                     if (sindex==0) { /* the default */
2683                         im->text_prop[TEXT_PROP_TITLE].size=size;
2684                         im->text_prop[TEXT_PROP_TITLE].font=font;
2685                         im->text_prop[TEXT_PROP_AXIS].size=size;
2686                         im->text_prop[TEXT_PROP_AXIS].font=font;
2687                         im->text_prop[TEXT_PROP_UNIT].size=size;
2688                         im->text_prop[TEXT_PROP_UNIT].font=font;
2689                         im->text_prop[TEXT_PROP_LEGEND].size=size;
2690                         im->text_prop[TEXT_PROP_LEGEND].font=font;
2691                     }
2692                 } else {
2693                     rrd_set_error("invalid fonttag '%s'",prop);
2694                     return;
2695                 }
2696             } else {
2697                 rrd_set_error("invalid text property format");
2698                 return;
2699             }
2700             break;          
2701         }
2702         case 'm':
2703             im->zoom= atof(optarg);
2704             if (im->zoom <= 0.0) {
2705                 rrd_set_error("zoom factor must be > 0");
2706                 return;
2707             }
2708           break;
2709         case 't':
2710             strncpy(im->title,optarg,150);
2711             im->title[150]='\0';
2712             break;
2713
2714         case '?':
2715             if (optopt != 0)
2716                 rrd_set_error("unknown option '%c'", optopt);
2717             else
2718                 rrd_set_error("unknown option '%s'",argv[optind-1]);
2719             return;
2720         }
2721     }
2722     
2723     if (optind >= argc) {
2724        rrd_set_error("missing filename");
2725        return;
2726     }
2727
2728     if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2729         rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");    
2730         return;
2731     }
2732
2733     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2734         /* error string is set in parsetime.c */
2735         return;
2736     }  
2737     
2738     if (start_tmp < 3600*24*365*10){
2739         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2740         return;
2741     }
2742     
2743     if (end_tmp < start_tmp) {
2744         rrd_set_error("start (%ld) should be less than end (%ld)", 
2745                start_tmp, end_tmp);
2746         return;
2747     }
2748     
2749     im->start = start_tmp;
2750     im->end = end_tmp;
2751 }
2752
2753 void
2754 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2755 {
2756     int         i;
2757     char        symname[100];
2758     int         linepass = 0; /* stack must follow LINE*, AREA or STACK */    
2759
2760     for (i=optind+1;i<argc;i++) {
2761         int             argstart=0;
2762         int             strstart=0;
2763         graph_desc_t    *gdp;
2764         char            *line;
2765         char            funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2766         double          d;
2767         double          linewidth;
2768         int             j,k,l,m;
2769
2770         /* Each command is one element from *argv[], we call this "line".
2771         **
2772         ** Each command defines the most current gdes inside struct im.
2773         ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2774         */
2775         gdes_alloc(im);
2776         gdp=&im->gdes[im->gdes_c-1];
2777         line=argv[i];
2778
2779         /* function:newvname=string[:ds-name:CF]        for xDEF
2780         ** function:vname[#color[:string]]              for LINEx,AREA,STACK
2781         ** function:vname#color[:num[:string]]          for TICK
2782         ** function:vname-or-num#color[:string]         for xRULE,PART
2783         ** function:vname:CF:string                     for xPRINT
2784         ** function:string                              for COMMENT
2785         */
2786         argstart=0;
2787
2788         sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2789         if (argstart==0) {
2790             rrd_set_error("Cannot parse function in line: %s",line);
2791             im_free(im);
2792             return;
2793         }
2794         if(sscanf(funcname,"LINE%lf",&linewidth)){
2795                 im->gdes[im->gdes_c-1].gf = GF_LINE;
2796                 im->gdes[im->gdes_c-1].linewidth = linewidth;
2797         } else {
2798           if ((gdp->gf=gf_conv(funcname))==-1) {
2799               rrd_set_error("'%s' is not a valid function name",funcname);
2800               im_free(im);
2801               return;
2802           }
2803         }
2804
2805         /* If the error string is set, we exit at the end of the switch */
2806         switch (gdp->gf) {
2807             case GF_COMMENT:
2808                 if (rrd_graph_legend(gdp,&line[argstart])==0)
2809                     rrd_set_error("Cannot parse comment in line: %s",line);
2810                 break;
2811             case GF_PART:
2812             case GF_VRULE:
2813             case GF_HRULE:
2814                 j=k=l=m=0;
2815                 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
2816                 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
2817                 if (k+m==0) {
2818                     rrd_set_error("Cannot parse name or num in line: %s",line);
2819                     break;
2820                 }
2821                 if (j!=0) {
2822                     gdp->xrule=d;
2823                     gdp->yrule=d;
2824                     argstart+=j;
2825                 } else if (!rrd_graph_check_vname(im,vname,line)) {
2826                     gdp->xrule=0;
2827                     gdp->yrule=DNAN;
2828                     argstart+=l;
2829                 } else break; /* exit due to wrong vname */
2830                 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
2831                 argstart+=j;
2832                 if (strlen(&line[argstart])!=0) {
2833                     if (rrd_graph_legend(gdp,&line[++argstart])==0)
2834                         rrd_set_error("Cannot parse comment in line: %s",line);
2835                 }
2836                 break;
2837             case GF_STACK:
2838                 if (linepass==0) {
2839                     rrd_set_error("STACK must follow another graphing element");
2840                     break;
2841                 }
2842             case GF_LINE:
2843             case GF_AREA:
2844             case GF_TICK:
2845                 j=k=0;
2846                 linepass=1;
2847                 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
2848                 if (j+1!=k)
2849                     rrd_set_error("Cannot parse vname in line: %s",line);
2850                 else if (rrd_graph_check_vname(im,vname,line))
2851                     rrd_set_error("Undefined vname '%s' in line: %s",line);
2852                 else
2853                     k=rrd_graph_color(im,&line[argstart],line,1);
2854                 if (rrd_test_error()) break;
2855                 argstart=argstart+j+k;
2856                 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
2857                     j=0;
2858                     sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
2859                     argstart+=j;
2860                 }
2861                 if (strlen(&line[argstart])!=0)
2862                     if (rrd_graph_legend(gdp,&line[++argstart])==0)
2863                         rrd_set_error("Cannot parse legend in line: %s",line);
2864                 break;
2865             case GF_PRINT:
2866                 im->prt_c++;
2867             case GF_GPRINT:
2868                 j=0;
2869                 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
2870                 if (j==0) {
2871                     rrd_set_error("Cannot parse vname in line: '%s'",line);
2872                     break;
2873                 }
2874                 argstart+=j;
2875                 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
2876                 j=0;
2877                 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
2878
2879                 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
2880 #define VIDX im->gdes[gdp->vidx]
2881                 switch (k) {
2882                     case -1: /* looks CF but is not really CF */
2883                         if (VIDX.gf == GF_VDEF) rrd_clear_error();
2884                         break;
2885                     case  0: /* CF present and correct */
2886                         if (VIDX.gf == GF_VDEF)
2887                             rrd_set_error("Don't use CF when printing VDEF");
2888                         argstart+=j;
2889                         break;
2890                     case  1: /* CF not present */
2891                         if (VIDX.gf == GF_VDEF) rrd_clear_error();
2892                         else rrd_set_error("Printing DEF or CDEF needs CF");
2893                         break;
2894                     default:
2895                         rrd_set_error("Oops, bug in GPRINT scanning");
2896                 }
2897 #undef VIDX
2898                 if (rrd_test_error()) break;
2899
2900                 if (strlen(&line[argstart])!=0) {
2901                     if (rrd_graph_legend(gdp,&line[argstart])==0)
2902                         rrd_set_error("Cannot parse legend in line: %s",line);
2903                 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
2904                 strcpy(gdp->format, gdp->legend);
2905                 break;
2906             case GF_DEF:
2907             case GF_VDEF:
2908             case GF_CDEF:
2909                 j=0;
2910                 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
2911                 if (j==0) {
2912                     rrd_set_error("Could not parse line: %s",line);
2913                     break;
2914                 }
2915                 if (find_var(im,gdp->vname)!=-1) {
2916                     rrd_set_error("Variable '%s' in line '%s' already in use\n",
2917                                                         gdp->vname,line);
2918                     break;
2919                 }
2920                 argstart+=j;
2921                 switch (gdp->gf) {
2922                     case GF_DEF:
2923                         argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
2924                         j=k=0;
2925                         sscanf(&line[argstart],
2926                                 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
2927                                 gdp->ds_nam, symname, &j, &k);
2928                         if ((j==0)||(k!=0)) {
2929                             rrd_set_error("Cannot parse DS or CF in '%s'",line);
2930                             break;
2931                         }
2932                         rrd_graph_check_CF(im,symname,line);
2933                         break;
2934                     case GF_VDEF:
2935                         j=0;
2936                         sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
2937                         if (j==0) {
2938                             rrd_set_error("Cannot parse vname in line '%s'",line);
2939                             break;
2940                         }
2941                         argstart+=j;
2942                         if (rrd_graph_check_vname(im,vname,line)) return;
2943                         if (       im->gdes[gdp->vidx].gf != GF_DEF
2944                                 && im->gdes[gdp->vidx].gf != GF_CDEF) {
2945                             rrd_set_error("variable '%s' not DEF nor "
2946                                 "CDEF in VDEF '%s'", vname,gdp->vname);
2947                             break;
2948                         }
2949                         vdef_parse(gdp,&line[argstart+strstart]);
2950                         break;
2951                     case GF_CDEF:
2952                         if (strstr(&line[argstart],":")!=NULL) {
2953                             rrd_set_error("Error in RPN, line: %s",line);
2954                             break;
2955                         }
2956                         if ((gdp->rpnp = rpn_parse(
2957                                                 (void *)im,
2958                                                 &line[argstart],
2959                                                 &find_var_wrapper)
2960                                 )==NULL)
2961                             rrd_set_error("invalid rpn expression in: %s",line);
2962                         break;
2963                     default: break;
2964                 }
2965                 break;
2966             default: rrd_set_error("Big oops");
2967         }
2968         if (rrd_test_error()) {
2969             im_free(im);
2970             return;
2971         }
2972     }
2973
2974     if (im->gdes_c==0){
2975         rrd_set_error("can't make a graph without contents");
2976         im_free(im); /* ??? is this set ??? */
2977         return; 
2978     }
2979 }
2980 int
2981 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
2982 {
2983     if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
2984         rrd_set_error("Unknown variable '%s' in %s",varname,err);
2985         return -1;
2986     }
2987     return 0;
2988 }
2989 int
2990 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
2991 {
2992     char *color;
2993     graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
2994
2995     color=strstr(var,"#");
2996     if (color==NULL) {
2997         if (optional==0) {
2998             rrd_set_error("Found no color in %s",err);
2999             return 0;
3000         }
3001         return 0;
3002     } else {
3003         int n=0;
3004         char *rest;
3005         gfx_color_t    col;
3006
3007         rest=strstr(color,":");
3008         if (rest!=NULL)
3009             n=rest-color;
3010         else
3011             n=strlen(color);
3012
3013         switch (n) {
3014             case 7:
3015                 sscanf(color,"#%6x%n",&col,&n);
3016                 col = (col << 8) + 0xff /* shift left by 8 */;
3017                 if (n!=7) rrd_set_error("Color problem in %s",err);
3018                 break;
3019             case 9:
3020                 sscanf(color,"#%8x%n",&col,&n);
3021                 if (n==9) break;
3022             default:
3023                 rrd_set_error("Color problem in %s",err);
3024         }
3025         if (rrd_test_error()) return 0;
3026         gdp->col = col;
3027         return n;
3028     }
3029 }
3030 int
3031 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
3032 {
3033     if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
3034         rrd_set_error("Unknown CF '%s' in %s",symname,err);
3035         return -1;
3036     }
3037     return 0;
3038 }
3039 int
3040 rrd_graph_legend(graph_desc_t *gdp, char *line)
3041 {
3042     int i;
3043
3044     i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3045
3046     return (strlen(&line[i])==0);
3047 }
3048
3049
3050 int bad_format(char *fmt) {
3051         char *ptr;
3052         int n=0;
3053
3054         ptr = fmt;
3055         while (*ptr != '\0') {
3056                 if (*ptr == '%') {ptr++;
3057                         if (*ptr == '\0') return 1;
3058                         while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') { 
3059                                 ptr++;
3060                         }
3061                         if (*ptr == '\0') return 1;
3062                         if (*ptr == 'l') {
3063                                 ptr++;
3064                                 n++;
3065                                 if (*ptr == '\0') return 1;
3066                                 if (*ptr == 'e' || *ptr == 'f') { 
3067                                         ptr++; 
3068                                         } else { return 1; }
3069                         }
3070                         else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3071                         else { return 1; }
3072                 } else {
3073                         ++ptr;
3074                 }
3075         }
3076         return (n!=1);
3077 }
3078 int
3079 vdef_parse(gdes,str)
3080 struct graph_desc_t *gdes;
3081 char *str;
3082 {
3083     /* A VDEF currently is either "func" or "param,func"
3084      * so the parsing is rather simple.  Change if needed.
3085      */
3086     double      param;
3087     char        func[30];
3088     int         n;
3089     
3090     n=0;
3091     sscanf(str,"%le,%29[A-Z]%n",&param,func,&n);
3092     if (n==strlen(str)) { /* matched */
3093         ;
3094     } else {
3095         n=0;
3096         sscanf(str,"%29[A-Z]%n",func,&n);
3097         if (n==strlen(str)) { /* matched */
3098             param=DNAN;
3099         } else {
3100             rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3101                 ,str
3102                 ,gdes->vname
3103                 );
3104             return -1;
3105         }
3106     }
3107     if          (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3108     else if     (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3109     else if     (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3110     else if     (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3111     else if     (!strcmp("TOTAL",  func)) gdes->vf.op = VDEF_TOTAL;
3112     else if     (!strcmp("FIRST",  func)) gdes->vf.op = VDEF_FIRST;
3113     else if     (!strcmp("LAST",   func)) gdes->vf.op = VDEF_LAST;
3114     else {
3115         rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3116             ,func
3117             ,gdes->vname
3118             );
3119         return -1;
3120     };
3121
3122     switch (gdes->vf.op) {
3123         case VDEF_PERCENT:
3124             if (isnan(param)) { /* no parameter given */
3125                 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3126                     ,func
3127                     ,gdes->vname
3128                     );
3129                 return -1;
3130             };
3131             if (param>=0.0 && param<=100.0) {
3132                 gdes->vf.param = param;
3133                 gdes->vf.val   = DNAN;  /* undefined */
3134                 gdes->vf.when  = 0;     /* undefined */
3135             } else {
3136                 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3137                     ,param
3138                     ,gdes->vname
3139                     );
3140                 return -1;
3141             };
3142             break;
3143         case VDEF_MAXIMUM:
3144         case VDEF_AVERAGE:
3145         case VDEF_MINIMUM:
3146         case VDEF_TOTAL:
3147         case VDEF_FIRST:
3148         case VDEF_LAST:
3149             if (isnan(param)) {
3150                 gdes->vf.param = DNAN;
3151                 gdes->vf.val   = DNAN;
3152                 gdes->vf.when  = 0;
3153             } else {
3154                 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3155                     ,func
3156                     ,gdes->vname
3157                     );
3158                 return -1;
3159             };
3160             break;
3161     };
3162     return 0;
3163 }
3164 int
3165 vdef_calc(im,gdi)
3166 image_desc_t *im;
3167 int gdi;
3168 {
3169     graph_desc_t        *src,*dst;
3170     rrd_value_t         *data;
3171     long                step,steps;
3172
3173     dst = &im->gdes[gdi];
3174     src = &im->gdes[dst->vidx];
3175     data = src->data + src->ds;
3176     steps = (src->end - src->start) / src->step;
3177
3178 #if 0
3179 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3180     ,src->start
3181     ,src->end
3182     ,steps
3183     );
3184 #endif
3185
3186     switch (dst->vf.op) {
3187         case VDEF_PERCENT: {
3188                 rrd_value_t *   array;
3189                 int             field;
3190
3191
3192                 if ((array = malloc(steps*sizeof(double)))==NULL) {
3193                     rrd_set_error("malloc VDEV_PERCENT");
3194                     return -1;
3195                 }
3196                 for (step=0;step < steps; step++) {
3197                     array[step]=data[step*src->ds_cnt];
3198                 }
3199                 qsort(array,step,sizeof(double),vdef_percent_compar);
3200
3201                 field = (steps-1)*dst->vf.param/100;
3202                 dst->vf.val  = array[field];
3203                 dst->vf.when = 0;       /* no time component */
3204 #if 0
3205 for(step=0;step<steps;step++)
3206 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3207 #endif
3208             }
3209             break;
3210         case VDEF_MAXIMUM:
3211             step=0;
3212             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3213             if (step == steps) {
3214                 dst->vf.val  = DNAN;
3215                 dst->vf.when = 0;
3216             } else {
3217                 dst->vf.val  = data[step*src->ds_cnt];
3218                 dst->vf.when = src->start + (step+1)*src->step;
3219             }
3220             while (step != steps) {
3221                 if (finite(data[step*src->ds_cnt])) {
3222                     if (data[step*src->ds_cnt] > dst->vf.val) {
3223                         dst->vf.val  = data[step*src->ds_cnt];
3224                         dst->vf.when = src->start + (step+1)*src->step;
3225                     }
3226                 }
3227                 step++;
3228             }
3229             break;
3230         case VDEF_TOTAL:
3231         case VDEF_AVERAGE: {
3232             int cnt=0;
3233             double sum=0.0;
3234             for (step=0;step<steps;step++) {
3235                 if (finite(data[step*src->ds_cnt])) {
3236                     sum += data[step*src->ds_cnt];
3237                     cnt ++;
3238                 };
3239             }
3240             if (cnt) {
3241                 if (dst->vf.op == VDEF_TOTAL) {
3242                     dst->vf.val  = sum*src->step;
3243                     dst->vf.when = cnt*src->step;       /* not really "when" */
3244                 } else {
3245                     dst->vf.val = sum/cnt;
3246                     dst->vf.when = 0;   /* no time component */
3247                 };
3248             } else {
3249                 dst->vf.val  = DNAN;
3250                 dst->vf.when = 0;
3251             }
3252             }
3253             break;
3254         case VDEF_MINIMUM:
3255             step=0;
3256             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3257             if (step == steps) {
3258                 dst->vf.val  = DNAN;
3259                 dst->vf.when = 0;
3260             } else {
3261                 dst->vf.val  = data[step*src->ds_cnt];
3262                 dst->vf.when = src->start + (step+1)*src->step;
3263             }
3264             while (step != steps) {
3265                 if (finite(data[step*src->ds_cnt])) {
3266                     if (data[step*src->ds_cnt] < dst->vf.val) {
3267                         dst->vf.val  = data[step*src->ds_cnt];
3268                         dst->vf.when = src->start + (step+1)*src->step;
3269                     }
3270                 }
3271                 step++;
3272             }
3273             break;
3274         case VDEF_FIRST:
3275             /* The time value returned here is one step before the
3276              * actual time value.  This is the start of the first
3277              * non-NaN interval.
3278              */
3279             step=0;
3280             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3281             if (step == steps) { /* all entries were NaN */
3282                 dst->vf.val  = DNAN;
3283                 dst->vf.when = 0;
3284             } else {
3285                 dst->vf.val  = data[step*src->ds_cnt];
3286                 dst->vf.when = src->start + step*src->step;
3287             }
3288             break;
3289         case VDEF_LAST:
3290             /* The time value returned here is the
3291              * actual time value.  This is the end of the last
3292              * non-NaN interval.
3293              */
3294             step=steps-1;
3295             while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3296             if (step < 0) { /* all entries were NaN */
3297                 dst->vf.val  = DNAN;
3298                 dst->vf.when = 0;
3299             } else {
3300                 dst->vf.val  = data[step*src->ds_cnt];
3301                 dst->vf.when = src->start + (step+1)*src->step;
3302             }
3303             break;
3304     }
3305     return 0;
3306 }
3307
3308 /* NaN < -INF < finite_values < INF */
3309 int
3310 vdef_percent_compar(a,b)
3311 const void *a,*b;
3312 {
3313     /* Equality is not returned; this doesn't hurt except
3314      * (maybe) for a little performance.
3315      */
3316
3317     /* First catch NaN values. They are smallest */
3318     if (isnan( *(double *)a )) return -1;
3319     if (isnan( *(double *)b )) return  1;
3320
3321     /* NaN doesn't reach this part so INF and -INF are extremes.
3322      * The sign from isinf() is compatible with the sign we return
3323      */
3324     if (isinf( *(double *)a )) return isinf( *(double *)a );
3325     if (isinf( *(double *)b )) return isinf( *(double *)b );
3326
3327     /* If we reach this, both values must be finite */
3328     if ( *(double *)a < *(double *)b ) return -1; else return 1;
3329 }