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