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