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