Changed the way circle sections are drawn.
[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
1848 /* draw that picture thing ... */
1849 int
1850 graph_paint(image_desc_t *im, char ***calcpr)
1851 {
1852   int i,ii;
1853   int lazy =     lazy_check(im);
1854   int piechart = 0;
1855   double PieStart=0.0, PieSize, PieCenterX, PieCenterY;
1856   FILE  *fo;
1857   gfx_canvas_t *canvas;
1858   gfx_node_t *node;
1859   
1860   double areazero = 0.0;
1861   enum gf_en stack_gf = GF_PRINT;
1862   graph_desc_t *lastgdes = NULL;    
1863   
1864   /* if we are lazy and there is nothing to PRINT ... quit now */
1865   if (lazy && im->prt_c==0) return 0;
1866   
1867   /* pull the data from the rrd files ... */
1868   
1869   if(data_fetch(im)==-1)
1870     return -1;
1871   
1872   /* evaluate VDEF and CDEF operations ... */
1873   if(data_calc(im)==-1)
1874     return -1;
1875   
1876   /* calculate and PRINT and GPRINT definitions. We have to do it at
1877    * this point because it will affect the length of the legends
1878    * if there are no graph elements we stop here ... 
1879    * if we are lazy, try to quit ... 
1880    */
1881   i=print_calc(im,calcpr);
1882   if(i<0) return -1;
1883   if(i==0 || lazy) return 0;
1884   
1885   /* get actual drawing data and find min and max values*/
1886   if(data_proc(im)==-1)
1887     return -1;
1888   
1889   if(!im->logarithmic){si_unit(im);}        /* identify si magnitude Kilo, Mega Giga ? */
1890   
1891   if(!im->rigid && ! im->logarithmic)
1892     expand_range(im);   /* make sure the upper and lower limit are
1893                            sensible values */
1894   
1895   /* init xtr and ytr */
1896   /* determine the actual size of the gif to draw. The size given
1897      on the cmdline is the graph area. But we need more as we have
1898      draw labels and other things outside the graph area */
1899   
1900   
1901   im->xorigin = 10 + 9 *  im->text_prop[TEXT_PROP_LEGEND].size;
1902
1903   xtr(im,0); 
1904   
1905   im->yorigin = 10 + im->ysize;
1906
1907   ytr(im,DNAN);
1908   
1909   if(im->title[0] != '\0')
1910     im->yorigin += im->text_prop[TEXT_PROP_TITLE].size*3+4;
1911   
1912   im->xgif= 20 +im->xsize + im->xorigin;
1913   im->ygif= im->yorigin+2* im->text_prop[TEXT_PROP_LEGEND].size;
1914
1915   /* check if we need to draw a piechart */
1916   for(i=0;i<im->gdes_c;i++){
1917     if (im->gdes[i].gf == GF_PART) {
1918       piechart=1;
1919       break;
1920     }
1921   }
1922
1923   if (piechart) {
1924         /* allocate enough space for the piechart itself (PieSize), 20%
1925         ** more for the background and an additional 50 pixels spacing.
1926         */
1927     if (im->xsize < im->ysize)
1928         PieSize = im->xsize;
1929     else
1930         PieSize = im->ysize;
1931     im->xgif += PieSize*1.2 + 50;
1932
1933     PieCenterX = im->xorigin + im->xsize + 50 + PieSize*0.6;
1934     PieCenterY = im->yorigin - PieSize*0.5;
1935   }
1936
1937   /* determine where to place the legends onto the graphics.
1938      and set im->ygif to match space requirements for text */
1939   if(leg_place(im)==-1)
1940     return -1;
1941
1942   canvas=gfx_new_canvas();
1943
1944
1945   /* the actual graph is created by going through the individual
1946      graph elements and then drawing them */
1947   
1948   node=gfx_new_area ( canvas,
1949                       0, 0,
1950                       im->xgif, 0,
1951                       im->xgif, im->ygif,
1952                       im->graph_col[GRC_BACK]);
1953
1954   gfx_add_point(node,0, im->ygif);
1955
1956   node=gfx_new_area ( canvas,
1957                       im->xorigin,             im->yorigin, 
1958                       im->xorigin + im->xsize, im->yorigin,
1959                       im->xorigin + im->xsize, im->yorigin-im->ysize,
1960                       im->graph_col[GRC_CANVAS]);
1961   
1962   gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
1963
1964 #if 0
1965 /******************************************************************
1966  ** Just to play around.  If you see this, I forgot to remove it **
1967  ******************************************************************/
1968   im->ygif+=100;
1969   node=gfx_new_area(canvas,
1970                         0,              im->ygif-100,
1971                         im->xgif,       im->ygif-100,
1972                         im->xgif,       im->ygif,
1973                         im->graph_col[GRC_CANVAS]);
1974   gfx_add_point(node,0,im->ygif);
1975
1976   node=gfx_new_line (canvas,
1977                 0,              im->ygif-100,
1978                 im->xgif-3,     im->ygif-100,
1979                 1.0,
1980                 0xFF0000FF);
1981   gfx_add_point(node,im->xgif-3,im->ygif-3);
1982   gfx_add_point(node,2,im->ygif-3);
1983   gfx_add_point(node,2,im->ygif-100);
1984
1985
1986 #if 0
1987   node=gfx_new_area ( canvas,
1988                         1,              im->ygif-99,
1989                         im->xgif-1,     im->ygif-99,
1990                         im->xgif-1,     im->ygif-1,
1991                         im->graph_col[GRC_CANVAS]);
1992   gfx_add_point(node,1,im->ygif-1);
1993 #endif
1994
1995 #endif
1996
1997   if (piechart) {
1998         int n;
1999
2000         node=gfx_new_area(canvas,
2001                 PieCenterX,PieCenterY-PieSize*0.6,
2002                 PieCenterX,PieCenterY,
2003                 PieCenterX,PieCenterY-PieSize*0.6,
2004                 im->graph_col[GRC_CANVAS]);
2005         for (n=1;n<500;n++) {
2006                 double angle;
2007                 angle=M_PI*2.0*n/500.0;
2008                 gfx_add_point(node,
2009                 PieCenterX+sin(angle)*PieSize*0.6,
2010                 PieCenterY-cos(angle)*PieSize*0.6 );
2011         }
2012   }
2013
2014   if (im->minval > 0.0)
2015     areazero = im->minval;
2016   if (im->maxval < 0.0)
2017     areazero = im->maxval;
2018   
2019   axis_paint(im,canvas);
2020
2021
2022   for(i=0;i<im->gdes_c;i++){
2023     switch(im->gdes[i].gf){
2024     case GF_CDEF:
2025     case GF_VDEF:
2026     case GF_DEF:
2027     case GF_PRINT:
2028     case GF_GPRINT:
2029     case GF_COMMENT:
2030     case GF_HRULE:
2031     case GF_VRULE:
2032       break;
2033     case GF_TICK:
2034       for (ii = 0; ii < im->xsize; ii++)
2035         {
2036           if (!isnan(im->gdes[i].p_data[ii]) && 
2037               im->gdes[i].p_data[ii] > 0.0)
2038             { 
2039               /* generate a tick */
2040               gfx_new_line(canvas, im -> xorigin + ii, 
2041                            im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2042                            im -> xorigin + ii, 
2043                            im -> yorigin,
2044                            1.0,
2045                            im -> gdes[i].col );
2046             }
2047         }
2048       break;
2049     case GF_LINE:
2050     case GF_AREA:
2051       stack_gf = im->gdes[i].gf;
2052     case GF_STACK:          
2053       /* fix data points at oo and -oo */
2054       for(ii=0;ii<im->xsize;ii++){
2055         if (isinf(im->gdes[i].p_data[ii])){
2056           if (im->gdes[i].p_data[ii] > 0) {
2057             im->gdes[i].p_data[ii] = im->maxval ;
2058           } else {
2059             im->gdes[i].p_data[ii] = im->minval ;
2060           }                 
2061           
2062         }
2063       } /* for */
2064       
2065       if (im->gdes[i].col != 0x0){               
2066         /* GF_LINE and friend */
2067         if(stack_gf == GF_LINE ){
2068           node = NULL;
2069           for(ii=1;ii<im->xsize;ii++){
2070             if ( ! isnan(im->gdes[i].p_data[ii-1])
2071                  && ! isnan(im->gdes[i].p_data[ii])){
2072               if (node == NULL){
2073                 node = gfx_new_line(canvas,
2074                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2075                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2076                                     im->gdes[i].linewidth,
2077                                     im->gdes[i].col);
2078               } else {
2079                 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2080               }
2081             } else {
2082               node = NULL;
2083             }
2084           }
2085         } else {
2086           int area_start=-1;
2087           node = NULL;
2088           for(ii=1;ii<im->xsize;ii++){
2089             /* open an area */
2090             if ( ! isnan(im->gdes[i].p_data[ii-1])
2091                  && ! isnan(im->gdes[i].p_data[ii])){
2092               if (node == NULL){
2093                 float ybase = 0.0;
2094                 if (im->gdes[i].gf == GF_STACK) {
2095                   ybase = ytr(im,lastgdes->p_data[ii-1]);
2096                 } else {
2097                   ybase =  ytr(im,areazero);
2098                 }
2099                 area_start = ii-1;
2100                 node = gfx_new_area(canvas,
2101                                     ii-1+im->xorigin,ybase,
2102                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2103                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2104                                     im->gdes[i].col
2105                                     );
2106               } else {
2107                 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2108               }
2109             }
2110
2111             if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2112               /* GF_AREA STACK type*/
2113               if (im->gdes[i].gf == GF_STACK ) {
2114                 int iii;
2115                 for (iii=ii-1;iii>area_start;iii--){
2116                   gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2117                 }
2118               } else {
2119                 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2120               };
2121               node=NULL;
2122             };
2123           }             
2124         } /* else GF_LINE */
2125       } /* if color != 0x0 */
2126       /* make sure we do not run into trouble when stacking on NaN */
2127       for(ii=0;ii<im->xsize;ii++){
2128         if (isnan(im->gdes[i].p_data[ii])) {
2129           double ybase = 0.0;
2130           if (lastgdes) {
2131             ybase = ytr(im,lastgdes->p_data[ii-1]);
2132           };
2133           if (isnan(ybase) || !lastgdes ){
2134             ybase =  ytr(im,areazero);
2135           }
2136           im->gdes[i].p_data[ii] = ybase;
2137         }
2138       } 
2139       lastgdes = &(im->gdes[i]);                         
2140       break;
2141     case GF_PART:
2142       if(isnan(im->gdes[i].yrule)) /* fetch variable */
2143         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2144      
2145       if (finite(im->gdes[i].yrule)) {  /* even the fetched var can be NaN */
2146         double angle,endangle;
2147         int n;
2148
2149         angle=M_PI*2.0*PieStart/100.0;
2150         endangle=M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0;
2151         node=gfx_new_area(canvas,
2152                 PieCenterX+sin(endangle)*PieSize/2,
2153                 PieCenterY-cos(endangle)*PieSize/2,
2154                 PieCenterX,
2155                 PieCenterY,
2156                 PieCenterX+sin(angle)*PieSize/2,
2157                 PieCenterY-cos(angle)*PieSize/2,
2158                 im->gdes[i].col);
2159         for (n=1;n<100;n++) {
2160                 angle=M_PI*2.0*(PieStart+n/100.0*im->gdes[i].yrule)/100.0;
2161                 gfx_add_point(node,
2162                 PieCenterX+sin(angle)*PieSize/2,
2163                 PieCenterY-cos(angle)*PieSize/2 );
2164         }
2165         PieStart += im->gdes[i].yrule;
2166       }
2167       break;
2168     } /* switch */
2169   }
2170   grid_paint(im,canvas);
2171   
2172   /* the RULES are the last thing to paint ... */
2173   for(i=0;i<im->gdes_c;i++){    
2174     
2175     switch(im->gdes[i].gf){
2176     case GF_HRULE:
2177       if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2178         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2179       };
2180       if(im->gdes[i].yrule >= im->minval
2181          && im->gdes[i].yrule <= im->maxval)
2182         gfx_new_line(canvas,
2183                      im->xorigin,ytr(im,im->gdes[i].yrule),
2184                      im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2185                      1.0,im->gdes[i].col); 
2186       break;
2187     case GF_VRULE:
2188       if(im->gdes[i].xrule == 0) { /* fetch variable */
2189         im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2190       };
2191       if(im->gdes[i].xrule >= im->start
2192          && im->gdes[i].xrule <= im->end)
2193         gfx_new_line(canvas,
2194                      xtr(im,im->gdes[i].xrule),im->yorigin,
2195                      xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2196                      1.0,im->gdes[i].col); 
2197       break;
2198     default:
2199       break;
2200     }
2201   }
2202
2203   
2204   if (strcmp(im->graphfile,"-")==0) {
2205 #ifdef WIN32
2206     /* Change translation mode for stdout to BINARY */
2207     _setmode( _fileno( stdout ), O_BINARY );
2208 #endif
2209     fo = stdout;
2210   } else {
2211     if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2212       rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2213                     strerror(errno));
2214       return (-1);
2215     }
2216   }
2217   switch (im->imgformat) {
2218   case IF_GIF:
2219     break;
2220   case IF_PNG:
2221     gfx_render_png (canvas,im->xgif,im->ygif,im->zoom,0x0,fo);
2222     break;
2223   }
2224   if (strcmp(im->graphfile,"-") != 0)
2225     fclose(fo);
2226    
2227   gfx_destroy(canvas);
2228   return 0;
2229 }
2230
2231
2232 /*****************************************************
2233  * graph stuff 
2234  *****************************************************/
2235
2236 int
2237 gdes_alloc(image_desc_t *im){
2238
2239     long def_step = (im->end-im->start)/im->xsize;
2240     
2241     if (im->step > def_step) /* step can be increassed ... no decreassed */
2242       def_step = im->step;
2243
2244     im->gdes_c++;
2245     
2246     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2247                                            * sizeof(graph_desc_t)))==NULL){
2248         rrd_set_error("realloc graph_descs");
2249         return -1;
2250     }
2251
2252
2253     im->gdes[im->gdes_c-1].step=def_step; 
2254     im->gdes[im->gdes_c-1].start=im->start; 
2255     im->gdes[im->gdes_c-1].end=im->end; 
2256     im->gdes[im->gdes_c-1].vname[0]='\0'; 
2257     im->gdes[im->gdes_c-1].data=NULL;
2258     im->gdes[im->gdes_c-1].ds_namv=NULL;
2259     im->gdes[im->gdes_c-1].data_first=0;
2260     im->gdes[im->gdes_c-1].p_data=NULL;
2261     im->gdes[im->gdes_c-1].rpnp=NULL;
2262     im->gdes[im->gdes_c-1].col = 0x0;
2263     im->gdes[im->gdes_c-1].legend[0]='\0';
2264     im->gdes[im->gdes_c-1].rrd[0]='\0';
2265     im->gdes[im->gdes_c-1].ds=-1;    
2266     im->gdes[im->gdes_c-1].p_data=NULL;    
2267     return 0;
2268 }
2269
2270 /* copies input untill the first unescaped colon is found
2271    or until input ends. backslashes have to be escaped as well */
2272 int
2273 scan_for_col(char *input, int len, char *output)
2274 {
2275     int inp,outp=0;
2276     for (inp=0; 
2277          inp < len &&
2278            input[inp] != ':' &&
2279            input[inp] != '\0';
2280          inp++){
2281       if (input[inp] == '\\' &&
2282           input[inp+1] != '\0' && 
2283           (input[inp+1] == '\\' ||
2284            input[inp+1] == ':')){
2285         output[outp++] = input[++inp];
2286       }
2287       else {
2288         output[outp++] = input[inp];
2289       }
2290     }
2291     output[outp] = '\0';
2292     return inp;
2293 }
2294
2295 /* Some surgery done on this function, it became ridiculously big.
2296 ** Things moved:
2297 ** - initializing     now in rrd_graph_init()
2298 ** - options parsing  now in rrd_graph_options()
2299 ** - script parsing   now in rrd_graph_script()
2300 */
2301 int 
2302 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2303 {
2304     image_desc_t   im;
2305
2306     rrd_graph_init(&im);
2307
2308     rrd_graph_options(argc,argv,&im);
2309     if (rrd_test_error()) return -1;
2310     
2311     if (strlen(argv[optind])>=MAXPATH) {
2312         rrd_set_error("filename (including path) too long");
2313         return -1;
2314     }
2315     strncpy(im.graphfile,argv[optind],MAXPATH-1);
2316     im.graphfile[MAXPATH-1]='\0';
2317
2318     rrd_graph_script(argc,argv,&im);
2319     if (rrd_test_error()) return -1;
2320
2321     /* Everything is now read and the actual work can start */
2322
2323     (*prdata)=NULL;
2324     if (graph_paint(&im,prdata)==-1){
2325         im_free(&im);
2326         return -1;
2327     }
2328
2329     /* The image is generated and needs to be output.
2330     ** Also, if needed, print a line with information about the image.
2331     */
2332
2333     *xsize=im.xgif;
2334     *ysize=im.ygif;
2335     if (im.imginfo) {
2336         char *filename;
2337         if (!(*prdata)) {
2338             /* maybe prdata is not allocated yet ... lets do it now */
2339             if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2340                 rrd_set_error("malloc imginfo");
2341                 return -1; 
2342             };
2343         }
2344         if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2345          ==NULL){
2346             rrd_set_error("malloc imginfo");
2347             return -1;
2348         }
2349         filename=im.graphfile+strlen(im.graphfile);
2350         while(filename > im.graphfile) {
2351             if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2352             filename--;
2353         }
2354
2355         sprintf((*prdata)[0],im.imginfo,filename,(long)(im.zoom*im.xgif),(long)(im.zoom*im.ygif));
2356     }
2357     im_free(&im);
2358     return 0;
2359 }
2360
2361 void
2362 rrd_graph_init(image_desc_t *im)
2363 {
2364     int i;
2365
2366     im->xlab_user.minsec = -1;
2367     im->xgif=0;
2368     im->ygif=0;
2369     im->xsize = 400;
2370     im->ysize = 100;
2371     im->step = 0;
2372     im->ylegend[0] = '\0';
2373     im->title[0] = '\0';
2374     im->minval = DNAN;
2375     im->maxval = DNAN;    
2376     im->interlaced = 0;
2377     im->unitsexponent= 9999;
2378     im->extra_flags= 0;
2379     im->rigid = 0;
2380     im->imginfo = NULL;
2381     im->lazy = 0;
2382     im->logarithmic = 0;
2383     im->ygridstep = DNAN;
2384     im->draw_x_grid = 1;
2385     im->draw_y_grid = 1;
2386     im->base = 1000;
2387     im->prt_c = 0;
2388     im->gdes_c = 0;
2389     im->gdes = NULL;
2390     im->zoom = 1.0;
2391     im->imgformat = IF_GIF; /* we default to GIF output */
2392
2393     for(i=0;i<DIM(graph_col);i++)
2394         im->graph_col[i]=graph_col[i];
2395
2396     for(i=0;i<DIM(text_prop);i++){        
2397       im->text_prop[i].size = text_prop[i].size;
2398       im->text_prop[i].font = text_prop[i].font;
2399     }
2400 }
2401
2402 void
2403 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2404 {
2405     int                 stroff;    
2406     char                *parsetime_error = NULL;
2407     char                scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2408     time_t              start_tmp=0,end_tmp=0;
2409     long                long_tmp;
2410     struct time_value   start_tv, end_tv;
2411     gfx_color_t         color;
2412
2413     parsetime("end-24h", &start_tv);
2414     parsetime("now", &end_tv);
2415
2416     while (1){
2417         static struct option long_options[] =
2418         {
2419             {"start",      required_argument, 0,  's'},
2420             {"end",        required_argument, 0,  'e'},
2421             {"x-grid",     required_argument, 0,  'x'},
2422             {"y-grid",     required_argument, 0,  'y'},
2423             {"vertical-label",required_argument,0,'v'},
2424             {"width",      required_argument, 0,  'w'},
2425             {"height",     required_argument, 0,  'h'},
2426             {"interlaced", no_argument,       0,  'i'},
2427             {"upper-limit",required_argument, 0,  'u'},
2428             {"lower-limit",required_argument, 0,  'l'},
2429             {"rigid",      no_argument,       0,  'r'},
2430             {"base",       required_argument, 0,  'b'},
2431             {"logarithmic",no_argument,       0,  'o'},
2432             {"color",      required_argument, 0,  'c'},
2433             {"font",       required_argument, 0,  'n'},
2434             {"title",      required_argument, 0,  't'},
2435             {"imginfo",    required_argument, 0,  'f'},
2436             {"imgformat",  required_argument, 0,  'a'},
2437             {"lazy",       no_argument,       0,  'z'},
2438             {"zoom",       required_argument, 0,  'm'},
2439             {"no-legend",  no_argument,       0,  'g'},
2440             {"alt-y-grid", no_argument,       0,   257 },
2441             {"alt-autoscale", no_argument,    0,   258 },
2442             {"alt-autoscale-max", no_argument,    0,   259 },
2443             {"units-exponent",required_argument, 0,  260},
2444             {"step",       required_argument, 0,   261},
2445             {0,0,0,0}};
2446         int option_index = 0;
2447         int opt;
2448
2449
2450         opt = getopt_long(argc, argv, 
2451                           "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2452                           long_options, &option_index);
2453
2454         if (opt == EOF)
2455             break;
2456         
2457         switch(opt) {
2458         case 257:
2459             im->extra_flags |= ALTYGRID;
2460             break;
2461         case 258:
2462             im->extra_flags |= ALTAUTOSCALE;
2463             break;
2464         case 259:
2465             im->extra_flags |= ALTAUTOSCALE_MAX;
2466             break;
2467         case 'g':
2468             im->extra_flags |= NOLEGEND;
2469             break;
2470         case 260:
2471             im->unitsexponent = atoi(optarg);
2472             break;
2473         case 261:
2474             im->step =  atoi(optarg);
2475             break;
2476         case 's':
2477             if ((parsetime_error = parsetime(optarg, &start_tv))) {
2478                 rrd_set_error( "start time: %s", parsetime_error );
2479                 return;
2480             }
2481             break;
2482         case 'e':
2483             if ((parsetime_error = parsetime(optarg, &end_tv))) {
2484                 rrd_set_error( "end time: %s", parsetime_error );
2485                 return;
2486             }
2487             break;
2488         case 'x':
2489             if(strcmp(optarg,"none") == 0){
2490               im->draw_x_grid=0;
2491               break;
2492             };
2493                 
2494             if(sscanf(optarg,
2495                       "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2496                       scan_gtm,
2497                       &im->xlab_user.gridst,
2498                       scan_mtm,
2499                       &im->xlab_user.mgridst,
2500                       scan_ltm,
2501                       &im->xlab_user.labst,
2502                       &im->xlab_user.precis,
2503                       &stroff) == 7 && stroff != 0){
2504                 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2505                 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2506                     rrd_set_error("unknown keyword %s",scan_gtm);
2507                     return;
2508                 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2509                     rrd_set_error("unknown keyword %s",scan_mtm);
2510                     return;
2511                 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2512                     rrd_set_error("unknown keyword %s",scan_ltm);
2513                     return;
2514                 } 
2515                 im->xlab_user.minsec = 1;
2516                 im->xlab_user.stst = im->xlab_form;
2517             } else {
2518                 rrd_set_error("invalid x-grid format");
2519                 return;
2520             }
2521             break;
2522         case 'y':
2523
2524             if(strcmp(optarg,"none") == 0){
2525               im->draw_y_grid=0;
2526               break;
2527             };
2528
2529             if(sscanf(optarg,
2530                       "%lf:%d",
2531                       &im->ygridstep,
2532                       &im->ylabfact) == 2) {
2533                 if(im->ygridstep<=0){
2534                     rrd_set_error("grid step must be > 0");
2535                     return;
2536                 } else if (im->ylabfact < 1){
2537                     rrd_set_error("label factor must be > 0");
2538                     return;
2539                 } 
2540             } else {
2541                 rrd_set_error("invalid y-grid format");
2542                 return;
2543             }
2544             break;
2545         case 'v':
2546             strncpy(im->ylegend,optarg,150);
2547             im->ylegend[150]='\0';
2548             break;
2549         case 'u':
2550             im->maxval = atof(optarg);
2551             break;
2552         case 'l':
2553             im->minval = atof(optarg);
2554             break;
2555         case 'b':
2556             im->base = atol(optarg);
2557             if(im->base != 1024 && im->base != 1000 ){
2558                 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2559                 return;
2560             }
2561             break;
2562         case 'w':
2563             long_tmp = atol(optarg);
2564             if (long_tmp < 10) {
2565                 rrd_set_error("width below 10 pixels");
2566                 return;
2567             }
2568             im->xsize = long_tmp;
2569             break;
2570         case 'h':
2571             long_tmp = atol(optarg);
2572             if (long_tmp < 10) {
2573                 rrd_set_error("height below 10 pixels");
2574                 return;
2575             }
2576             im->ysize = long_tmp;
2577             break;
2578         case 'i':
2579             im->interlaced = 1;
2580             break;
2581         case 'r':
2582             im->rigid = 1;
2583             break;
2584         case 'f':
2585             im->imginfo = optarg;
2586             break;
2587         case 'a':
2588             if((im->imgformat = if_conv(optarg)) == -1) {
2589                 rrd_set_error("unsupported graphics format '%s'",optarg);
2590                 return;
2591             }
2592             break;
2593         case 'z':
2594             im->lazy = 1;
2595             break;
2596         case 'o':
2597             im->logarithmic = 1;
2598             if (isnan(im->minval))
2599                 im->minval=1;
2600             break;
2601         case 'c':
2602             if(sscanf(optarg,
2603                       "%10[A-Z]#%8x",
2604                       col_nam,&color) == 2){
2605                 int ci;
2606                 if((ci=grc_conv(col_nam)) != -1){
2607                     im->graph_col[ci]=color;
2608                 }  else {
2609                   rrd_set_error("invalid color name '%s'",col_nam);
2610                 }
2611             } else {
2612                 rrd_set_error("invalid color def format");
2613                 return -1;
2614             }
2615             break;        
2616         case 'n':{
2617                         /* originally this used char *prop = "" and
2618                         ** char *font = "dummy" however this results
2619                         ** in a SEG fault, at least on RH7.1
2620                         **
2621                         ** The current implementation isn't proper
2622                         ** either, font is never freed and prop uses
2623                         ** a fixed width string
2624                         */
2625             char prop[100];
2626             double size = 1;
2627             char *font;
2628
2629             font=malloc(255);
2630             if(sscanf(optarg,
2631                                 "%10[A-Z]:%lf:%s",
2632                                 prop,&size,font) == 3){
2633                 int sindex;
2634                 if((sindex=text_prop_conv(prop)) != -1){
2635                     im->text_prop[sindex].size=size;              
2636                     im->text_prop[sindex].font=font;
2637                     if (sindex==0) { /* the default */
2638                         im->text_prop[TEXT_PROP_TITLE].size=size;
2639                         im->text_prop[TEXT_PROP_TITLE].font=font;
2640                         im->text_prop[TEXT_PROP_AXIS].size=size;
2641                         im->text_prop[TEXT_PROP_AXIS].font=font;
2642                         im->text_prop[TEXT_PROP_UNIT].size=size;
2643                         im->text_prop[TEXT_PROP_UNIT].font=font;
2644                         im->text_prop[TEXT_PROP_LEGEND].size=size;
2645                         im->text_prop[TEXT_PROP_LEGEND].font=font;
2646                     }
2647                 } else {
2648                     rrd_set_error("invalid fonttag '%s'",prop);
2649                     return;
2650                 }
2651             } else {
2652                 rrd_set_error("invalid text property format");
2653                 return;
2654             }
2655             break;          
2656         }
2657         case 'm':
2658             im->zoom= atof(optarg);
2659             if (im->zoom <= 0.0) {
2660                 rrd_set_error("zoom factor must be > 0");
2661                 return;
2662             }
2663           break;
2664         case 't':
2665             strncpy(im->title,optarg,150);
2666             im->title[150]='\0';
2667             break;
2668
2669         case '?':
2670             if (optopt != 0)
2671                 rrd_set_error("unknown option '%c'", optopt);
2672             else
2673                 rrd_set_error("unknown option '%s'",argv[optind-1]);
2674             return;
2675         }
2676     }
2677     
2678     if (optind >= argc) {
2679        rrd_set_error("missing filename");
2680        return;
2681     }
2682
2683     if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2684         rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");    
2685         return;
2686     }
2687
2688     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2689         /* error string is set in parsetime.c */
2690         return;
2691     }  
2692     
2693     if (start_tmp < 3600*24*365*10){
2694         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2695         return;
2696     }
2697     
2698     if (end_tmp < start_tmp) {
2699         rrd_set_error("start (%ld) should be less than end (%ld)", 
2700                start_tmp, end_tmp);
2701         return;
2702     }
2703     
2704     im->start = start_tmp;
2705     im->end = end_tmp;
2706 }
2707
2708 void
2709 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2710 {
2711     int         i;
2712     char        symname[100];
2713     int         linepass = 0; /* stack must follow LINE*, AREA or STACK */    
2714
2715     for (i=optind+1;i<argc;i++) {
2716         int             argstart=0;
2717         int             strstart=0;
2718         graph_desc_t    *gdp;
2719         char            *line;
2720         char            funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2721         double          d;
2722         double          linewidth;
2723         int             j,k,l,m;
2724
2725         /* Each command is one element from *argv[], we call this "line".
2726         **
2727         ** Each command defines the most current gdes inside struct im.
2728         ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2729         */
2730         gdes_alloc(im);
2731         gdp=&im->gdes[im->gdes_c-1];
2732         line=argv[i];
2733
2734         /* function:newvname=string[:ds-name:CF]        for xDEF
2735         ** function:vname[#color[:string]]              for LINEx,AREA,STACK
2736         ** function:vname#color[:num[:string]]          for TICK
2737         ** function:vname-or-num#color[:string]         for xRULE,PART
2738         ** function:vname:CF:string                     for xPRINT
2739         ** function:string                              for COMMENT
2740         */
2741         argstart=0;
2742
2743         sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2744         if (argstart==0) {
2745             rrd_set_error("Cannot parse function in line: %s",line);
2746             im_free(im);
2747             return;
2748         }
2749         if(sscanf(funcname,"LINE%lf",&linewidth)){
2750                 im->gdes[im->gdes_c-1].gf = GF_LINE;
2751                 im->gdes[im->gdes_c-1].linewidth = linewidth;
2752         } else {
2753           if ((gdp->gf=gf_conv(funcname))==-1) {
2754               rrd_set_error("'%s' is not a valid function name",funcname);
2755               im_free(im);
2756               return;
2757           }
2758         }
2759
2760         /* If the error string is set, we exit at the end of the switch */
2761         switch (gdp->gf) {
2762             case GF_COMMENT:
2763                 if (rrd_graph_legend(gdp,&line[argstart])==0)
2764                     rrd_set_error("Cannot parse comment in line: %s",line);
2765                 break;
2766             case GF_PART:
2767             case GF_VRULE:
2768             case GF_HRULE:
2769                 j=k=l=m=0;
2770                 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
2771                 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
2772                 if (k+m==0) {
2773                     rrd_set_error("Cannot parse name or num in line: %s",line);
2774                     break;
2775                 }
2776                 if (j!=0) {
2777                     gdp->xrule=d;
2778                     gdp->yrule=d;
2779                     argstart+=j;
2780                 } else if (!rrd_graph_check_vname(im,vname,line)) {
2781                     gdp->xrule=0;
2782                     gdp->yrule=DNAN;
2783                     argstart+=l;
2784                 } else break; /* exit due to wrong vname */
2785                 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
2786                 argstart+=j;
2787                 if (strlen(&line[argstart])!=0) {
2788                     if (rrd_graph_legend(gdp,&line[++argstart])==0)
2789                         rrd_set_error("Cannot parse comment in line: %s",line);
2790                 }
2791                 break;
2792             case GF_STACK:
2793                 if (linepass==0) {
2794                     rrd_set_error("STACK must follow another graphing element");
2795                     break;
2796                 }
2797             case GF_LINE:
2798             case GF_AREA:
2799             case GF_TICK:
2800                 j=k=0;
2801                 linepass=1;
2802                 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
2803                 if (j+1!=k)
2804                     rrd_set_error("Cannot parse vname in line: %s",line);
2805                 else if (rrd_graph_check_vname(im,vname,line))
2806                     rrd_set_error("Undefined vname '%s' in line: %s",line);
2807                 else
2808                     k=rrd_graph_color(im,&line[argstart],line,1);
2809                 if (rrd_test_error()) break;
2810                 argstart=argstart+j+k;
2811                 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
2812                     j=0;
2813                     sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
2814                     argstart+=j;
2815                 }
2816                 if (strlen(&line[argstart])!=0)
2817                     if (rrd_graph_legend(gdp,&line[++argstart])==0)
2818                         rrd_set_error("Cannot parse legend in line: %s",line);
2819                 break;
2820             case GF_PRINT:
2821                 im->prt_c++;
2822             case GF_GPRINT:
2823                 j=0;
2824                 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
2825                 if (j==0) {
2826                     rrd_set_error("Cannot parse vname in line: '%s'",line);
2827                     break;
2828                 }
2829                 argstart+=j;
2830                 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
2831                 j=0;
2832                 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
2833
2834                 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
2835 #define VIDX im->gdes[gdp->vidx]
2836                 switch (k) {
2837                     case -1: /* looks CF but is not really CF */
2838                         if (VIDX.gf == GF_VDEF) rrd_clear_error();
2839                         break;
2840                     case  0: /* CF present and correct */
2841                         if (VIDX.gf == GF_VDEF)
2842                             rrd_set_error("Don't use CF when printing VDEF");
2843                         argstart+=j;
2844                         break;
2845                     case  1: /* CF not present */
2846                         if (VIDX.gf == GF_VDEF) rrd_clear_error();
2847                         else rrd_set_error("Printing DEF or CDEF needs CF");
2848                         break;
2849                     default:
2850                         rrd_set_error("Oops, bug in GPRINT scanning");
2851                 }
2852 #undef VIDX
2853                 if (rrd_test_error()) break;
2854
2855                 if (strlen(&line[argstart])!=0) {
2856                     if (rrd_graph_legend(gdp,&line[argstart])==0)
2857                         rrd_set_error("Cannot parse legend in line: %s",line);
2858                 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
2859                 strcpy(gdp->format, gdp->legend);
2860                 break;
2861             case GF_DEF:
2862             case GF_VDEF:
2863             case GF_CDEF:
2864                 j=0;
2865                 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
2866                 if (j==0) {
2867                     rrd_set_error("Could not parse line: %s",line);
2868                     break;
2869                 }
2870                 if (find_var(im,gdp->vname)!=-1) {
2871                     rrd_set_error("Variable '%s' in line '%s' already in use\n",
2872                                                         gdp->vname,line);
2873                     break;
2874                 }
2875                 argstart+=j;
2876                 switch (gdp->gf) {
2877                     case GF_DEF:
2878                         argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
2879                         j=k=0;
2880                         sscanf(&line[argstart],
2881                                 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
2882                                 gdp->ds_nam, symname, &j, &k);
2883                         if ((j==0)||(k!=0)) {
2884                             rrd_set_error("Cannot parse DS or CF in '%s'",line);
2885                             break;
2886                         }
2887                         rrd_graph_check_CF(im,symname,line);
2888                         break;
2889                     case GF_VDEF:
2890                         j=0;
2891                         sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
2892                         if (j==0) {
2893                             rrd_set_error("Cannot parse vname in line '%s'",line);
2894                             break;
2895                         }
2896                         argstart+=j;
2897                         if (rrd_graph_check_vname(im,vname,line)) return;
2898                         if (       im->gdes[gdp->vidx].gf != GF_DEF
2899                                 && im->gdes[gdp->vidx].gf != GF_CDEF) {
2900                             rrd_set_error("variable '%s' not DEF nor "
2901                                 "CDEF in VDEF '%s'", vname,gdp->vname);
2902                             break;
2903                         }
2904                         vdef_parse(gdp,&line[argstart+strstart]);
2905                         break;
2906                     case GF_CDEF:
2907                         if (strstr(&line[argstart],":")!=NULL) {
2908                             rrd_set_error("Error in RPN, line: %s",line);
2909                             break;
2910                         }
2911                         if ((gdp->rpnp = rpn_parse(
2912                                                 (void *)im,
2913                                                 &line[argstart],
2914                                                 &find_var_wrapper)
2915                                 )==NULL)
2916                             rrd_set_error("invalid rpn expression in: %s",line);
2917                         break;
2918                     default: break;
2919                 }
2920                 break;
2921             default: rrd_set_error("Big oops");
2922         }
2923         if (rrd_test_error()) {
2924             im_free(im);
2925             return;
2926         }
2927     }
2928
2929     if (im->gdes_c==0){
2930         rrd_set_error("can't make a graph without contents");
2931         im_free(im); /* ??? is this set ??? */
2932         return; 
2933     }
2934 }
2935 int
2936 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
2937 {
2938     if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
2939         rrd_set_error("Unknown variable '%s' in %s",varname,err);
2940         return -1;
2941     }
2942     return 0;
2943 }
2944 int
2945 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
2946 {
2947     char *color;
2948     graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
2949
2950     color=strstr(var,"#");
2951     if (color==NULL) {
2952         if (optional==0) {
2953             rrd_set_error("Found no color in %s",err);
2954             return 0;
2955         }
2956         return 0;
2957     } else {
2958         int n=0;
2959         char *rest;
2960         gfx_color_t    col;
2961
2962         rest=strstr(color,":");
2963         if (rest!=NULL)
2964             n=rest-color;
2965         else
2966             n=strlen(color);
2967
2968         switch (n) {
2969             case 7:
2970                 sscanf(color,"#%6x%n",&col,&n);
2971                 col = (col << 8) + 0xff /* shift left by 8 */;
2972                 if (n!=7) rrd_set_error("Color problem in %s",err);
2973                 break;
2974             case 9:
2975                 sscanf(color,"#%8x%n",&col,&n);
2976                 if (n==9) break;
2977             default:
2978                 rrd_set_error("Color problem in %s",err);
2979         }
2980         if (rrd_test_error()) return 0;
2981         gdp->col = col;
2982         return n;
2983     }
2984 }
2985 int
2986 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
2987 {
2988     if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
2989         rrd_set_error("Unknown CF '%s' in %s",symname,err);
2990         return -1;
2991     }
2992     return 0;
2993 }
2994 int
2995 rrd_graph_legend(graph_desc_t *gdp, char *line)
2996 {
2997     int i;
2998
2999     i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3000
3001     return (strlen(&line[i])==0);
3002 }
3003
3004
3005 int bad_format(char *fmt) {
3006         char *ptr;
3007         int n=0;
3008
3009         ptr = fmt;
3010         while (*ptr != '\0') {
3011                 if (*ptr == '%') {ptr++;
3012                         if (*ptr == '\0') return 1;
3013                         while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') { 
3014                                 ptr++;
3015                         }
3016                         if (*ptr == '\0') return 1;
3017                         if (*ptr == 'l') {
3018                                 ptr++;
3019                                 n++;
3020                                 if (*ptr == '\0') return 1;
3021                                 if (*ptr == 'e' || *ptr == 'f') { 
3022                                         ptr++; 
3023                                         } else { return 1; }
3024                         }
3025                         else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3026                         else { return 1; }
3027                 } else {
3028                         ++ptr;
3029                 }
3030         }
3031         return (n!=1);
3032 }
3033 int
3034 vdef_parse(gdes,str)
3035 struct graph_desc_t *gdes;
3036 char *str;
3037 {
3038     /* A VDEF currently is either "func" or "param,func"
3039      * so the parsing is rather simple.  Change if needed.
3040      */
3041     double      param;
3042     char        func[30];
3043     int         n;
3044     
3045     n=0;
3046     sscanf(str,"%le,%29[A-Z]%n",&param,func,&n);
3047     if (n==strlen(str)) { /* matched */
3048         ;
3049     } else {
3050         n=0;
3051         sscanf(str,"%29[A-Z]%n",func,&n);
3052         if (n==strlen(str)) { /* matched */
3053             param=DNAN;
3054         } else {
3055             rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3056                 ,str
3057                 ,gdes->vname
3058                 );
3059             return -1;
3060         }
3061     }
3062     if          (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3063     else if     (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3064     else if     (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3065     else if     (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3066     else if     (!strcmp("TOTAL",  func)) gdes->vf.op = VDEF_TOTAL;
3067     else if     (!strcmp("FIRST",  func)) gdes->vf.op = VDEF_FIRST;
3068     else if     (!strcmp("LAST",   func)) gdes->vf.op = VDEF_LAST;
3069     else {
3070         rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3071             ,func
3072             ,gdes->vname
3073             );
3074         return -1;
3075     };
3076
3077     switch (gdes->vf.op) {
3078         case VDEF_PERCENT:
3079             if (isnan(param)) { /* no parameter given */
3080                 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3081                     ,func
3082                     ,gdes->vname
3083                     );
3084                 return -1;
3085             };
3086             if (param>=0.0 && param<=100.0) {
3087                 gdes->vf.param = param;
3088                 gdes->vf.val   = DNAN;  /* undefined */
3089                 gdes->vf.when  = 0;     /* undefined */
3090             } else {
3091                 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3092                     ,param
3093                     ,gdes->vname
3094                     );
3095                 return -1;
3096             };
3097             break;
3098         case VDEF_MAXIMUM:
3099         case VDEF_AVERAGE:
3100         case VDEF_MINIMUM:
3101         case VDEF_TOTAL:
3102         case VDEF_FIRST:
3103         case VDEF_LAST:
3104             if (isnan(param)) {
3105                 gdes->vf.param = DNAN;
3106                 gdes->vf.val   = DNAN;
3107                 gdes->vf.when  = 0;
3108             } else {
3109                 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3110                     ,func
3111                     ,gdes->vname
3112                     );
3113                 return -1;
3114             };
3115             break;
3116     };
3117     return 0;
3118 }
3119 int
3120 vdef_calc(im,gdi)
3121 image_desc_t *im;
3122 int gdi;
3123 {
3124     graph_desc_t        *src,*dst;
3125     rrd_value_t         *data;
3126     long                step,steps;
3127
3128     dst = &im->gdes[gdi];
3129     src = &im->gdes[dst->vidx];
3130     data = src->data + src->ds;
3131     steps = (src->end - src->start) / src->step;
3132
3133 #if 0
3134 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3135     ,src->start
3136     ,src->end
3137     ,steps
3138     );
3139 #endif
3140
3141     switch (dst->vf.op) {
3142         case VDEF_PERCENT: {
3143                 rrd_value_t *   array;
3144                 int             field;
3145
3146
3147                 if ((array = malloc(steps*sizeof(double)))==NULL) {
3148                     rrd_set_error("malloc VDEV_PERCENT");
3149                     return -1;
3150                 }
3151                 for (step=0;step < steps; step++) {
3152                     array[step]=data[step*src->ds_cnt];
3153                 }
3154                 qsort(array,step,sizeof(double),vdef_percent_compar);
3155
3156                 field = (steps-1)*dst->vf.param/100;
3157                 dst->vf.val  = array[field];
3158                 dst->vf.when = 0;       /* no time component */
3159 #if 0
3160 for(step=0;step<steps;step++)
3161 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3162 #endif
3163             }
3164             break;
3165         case VDEF_MAXIMUM:
3166             step=0;
3167             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3168             if (step == steps) {
3169                 dst->vf.val  = DNAN;
3170                 dst->vf.when = 0;
3171             } else {
3172                 dst->vf.val  = data[step*src->ds_cnt];
3173                 dst->vf.when = src->start + (step+1)*src->step;
3174             }
3175             while (step != steps) {
3176                 if (finite(data[step*src->ds_cnt])) {
3177                     if (data[step*src->ds_cnt] > dst->vf.val) {
3178                         dst->vf.val  = data[step*src->ds_cnt];
3179                         dst->vf.when = src->start + (step+1)*src->step;
3180                     }
3181                 }
3182                 step++;
3183             }
3184             break;
3185         case VDEF_TOTAL:
3186         case VDEF_AVERAGE: {
3187             int cnt=0;
3188             double sum=0.0;
3189             for (step=0;step<steps;step++) {
3190                 if (finite(data[step*src->ds_cnt])) {
3191                     sum += data[step*src->ds_cnt];
3192                     cnt ++;
3193                 };
3194             }
3195             if (cnt) {
3196                 if (dst->vf.op == VDEF_TOTAL) {
3197                     dst->vf.val  = sum*src->step;
3198                     dst->vf.when = cnt*src->step;       /* not really "when" */
3199                 } else {
3200                     dst->vf.val = sum/cnt;
3201                     dst->vf.when = 0;   /* no time component */
3202                 };
3203             } else {
3204                 dst->vf.val  = DNAN;
3205                 dst->vf.when = 0;
3206             }
3207             }
3208             break;
3209         case VDEF_MINIMUM:
3210             step=0;
3211             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3212             if (step == steps) {
3213                 dst->vf.val  = DNAN;
3214                 dst->vf.when = 0;
3215             } else {
3216                 dst->vf.val  = data[step*src->ds_cnt];
3217                 dst->vf.when = src->start + (step+1)*src->step;
3218             }
3219             while (step != steps) {
3220                 if (finite(data[step*src->ds_cnt])) {
3221                     if (data[step*src->ds_cnt] < dst->vf.val) {
3222                         dst->vf.val  = data[step*src->ds_cnt];
3223                         dst->vf.when = src->start + (step+1)*src->step;
3224                     }
3225                 }
3226                 step++;
3227             }
3228             break;
3229         case VDEF_FIRST:
3230             /* The time value returned here is one step before the
3231              * actual time value.  This is the start of the first
3232              * non-NaN interval.
3233              */
3234             step=0;
3235             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3236             if (step == steps) { /* all entries were NaN */
3237                 dst->vf.val  = DNAN;
3238                 dst->vf.when = 0;
3239             } else {
3240                 dst->vf.val  = data[step*src->ds_cnt];
3241                 dst->vf.when = src->start + step*src->step;
3242             }
3243             break;
3244         case VDEF_LAST:
3245             /* The time value returned here is the
3246              * actual time value.  This is the end of the last
3247              * non-NaN interval.
3248              */
3249             step=steps-1;
3250             while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3251             if (step < 0) { /* all entries were NaN */
3252                 dst->vf.val  = DNAN;
3253                 dst->vf.when = 0;
3254             } else {
3255                 dst->vf.val  = data[step*src->ds_cnt];
3256                 dst->vf.when = src->start + (step+1)*src->step;
3257             }
3258             break;
3259     }
3260     return 0;
3261 }
3262
3263 /* NaN < -INF < finite_values < INF */
3264 int
3265 vdef_percent_compar(a,b)
3266 const void *a,*b;
3267 {
3268     /* Equality is not returned; this doesn't hurt except
3269      * (maybe) for a little performance.
3270      */
3271
3272     /* First catch NaN values. They are smallest */
3273     if (isnan( *(double *)a )) return -1;
3274     if (isnan( *(double *)b )) return  1;
3275
3276     /* NaN doesn't reach this part so INF and -INF are extremes.
3277      * The sign from isinf() is compatible with the sign we return
3278      */
3279     if (isinf( *(double *)a )) return isinf( *(double *)a );
3280     if (isinf( *(double *)b )) return isinf( *(double *)b );
3281
3282     /* If we reach this, both values must be finite */
3283     if ( *(double *)a < *(double *)b ) return -1; else return 1;
3284 }