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