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