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