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