Changes in rrd_graph; see NEWS
[rrdtool.git] / src / rrd_graph.c
1 /****************************************************************************
2  * RRDtool 1.1.x  Copyright Tobias Oetiker, 1997 - 2002
3  ****************************************************************************
4  * rrd__graph.c  make creates ne rrds
5  ****************************************************************************/
6
7 #if 0
8 #include "rrd_tool.h"
9 #endif
10
11 #include <sys/stat.h>
12 #ifdef WIN32
13 #include <io.h>
14 #include <fcntl.h>
15 #endif
16
17 #include "rrd_graph.h"
18 #include "rrd_graph_helper.h"
19
20 /* some constant definitions */
21
22
23 #ifndef RRD_DEFAULT_FONT
24 #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf" 
25 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
26 #endif
27
28
29 text_prop_t text_prop[] = {   
30      { 10.0, RRD_DEFAULT_FONT }, /* default */
31      { 12.0, RRD_DEFAULT_FONT }, /* title */
32      { 8.0,  RRD_DEFAULT_FONT },  /* axis */
33      { 10.0, RRD_DEFAULT_FONT },  /* unit */
34      { 10.0, RRD_DEFAULT_FONT }  /* legend */
35 };
36
37 xlab_t xlab[] = {
38     {0,        TMT_SECOND,30, TMT_MINUTE,5,  TMT_MINUTE,5,         0,"%H:%M"},
39     {2,        TMT_MINUTE,1,  TMT_MINUTE,5,  TMT_MINUTE,5,         0,"%H:%M"},
40     {5,        TMT_MINUTE,2,  TMT_MINUTE,10, TMT_MINUTE,10,        0,"%H:%M"},
41     {10,       TMT_MINUTE,5,  TMT_MINUTE,20, TMT_MINUTE,20,        0,"%H:%M"},
42     {30,       TMT_MINUTE,10, TMT_HOUR,1,    TMT_HOUR,1,           0,"%H:%M"},
43     {60,       TMT_MINUTE,30, TMT_HOUR,2,    TMT_HOUR,2,           0,"%H:%M"},
44     {180,      TMT_HOUR,1,    TMT_HOUR,6,    TMT_HOUR,6,           0,"%H:%M"},
45     /*{300,      TMT_HOUR,3,    TMT_HOUR,12,   TMT_HOUR,12,    12*3600,"%a %p"},  this looks silly*/
46     {600,      TMT_HOUR,6,    TMT_DAY,1,     TMT_DAY,1,      24*3600,"%a"},
47     {1800,     TMT_HOUR,12,   TMT_DAY,1,     TMT_DAY,2,      24*3600,"%a"},
48     {3600,     TMT_DAY,1,     TMT_WEEK,1,     TMT_WEEK,1,    7*24*3600,"Week %V"},
49     {3*3600,   TMT_WEEK,1,      TMT_MONTH,1,     TMT_WEEK,2,    7*24*3600,"Week %V"},
50     {6*3600,   TMT_MONTH,1,   TMT_MONTH,1,   TMT_MONTH,1, 30*24*3600,"%b"},
51     {48*3600,  TMT_MONTH,1,   TMT_MONTH,3,   TMT_MONTH,3, 30*24*3600,"%b"},
52     {10*24*3600, TMT_YEAR,1,  TMT_YEAR,1,    TMT_YEAR,1, 365*24*3600,"%y"},
53     {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
54 };
55
56 /* sensible logarithmic y label intervals ...
57    the first element of each row defines the possible starting points on the
58    y axis ... the other specify the */
59
60 double yloglab[][12]= {{ 1e9, 1,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0 },
61                        {  1e3, 1,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0 },
62                        {  1e1, 1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
63                        /* {  1e1, 1,  5,  0,  0,  0,  0,  0,  0,  0,  0,  0 }, */
64                        {  1e1, 1,  2.5,  5,  7.5,  0,  0,  0,  0,  0,  0,  0 },
65                        {  1e1, 1,  2,  4,  6,  8,  0,  0,  0,  0,  0,  0 },
66                        {  1e1, 1,  2,  3,  4,  5,  6,  7,  8,  9,  0,  0 },
67                        {  0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 }};
68
69 /* sensible y label intervals ...*/
70
71 ylab_t ylab[]= {
72     {0.1, {1,2, 5,10}},
73     {0.2, {1,5,10,20}},
74     {0.5, {1,2, 4,10}},
75     {1.0,   {1,2, 5,10}},
76     {2.0,   {1,5,10,20}},
77     {5.0,   {1,2, 4,10}},
78     {10.0,  {1,2, 5,10}},
79     {20.0,  {1,5,10,20}},
80     {50.0,  {1,2, 4,10}},
81     {100.0, {1,2, 5,10}},
82     {200.0, {1,5,10,20}},
83     {500.0, {1,2, 4,10}},
84     {0.0,   {0,0,0,0}}};
85
86
87 gfx_color_t graph_col[] =   /* default colors */
88 {    0xFFFFFFFF,   /* canvas     */
89      0xF0F0F0FF,   /* background */
90      0xD0D0D0FF,   /* shade A    */
91      0xA0A0A0FF,   /* shade B    */
92      0x909090FF,   /* grid       */
93      0xE05050FF,   /* major grid */
94      0x000000FF,   /* font       */ 
95      0x000000FF,   /* frame      */
96      0xFF0000FF  /* arrow      */
97 };
98
99
100 /* #define DEBUG */
101
102 #ifdef DEBUG
103 # define DPRINT(x)    (void)(printf x, printf("\n"))
104 #else
105 # define DPRINT(x)
106 #endif
107
108
109 /* initialize with xtr(im,0); */
110 int
111 xtr(image_desc_t *im,time_t mytime){
112     static double pixie;
113     if (mytime==0){
114         pixie = (double) im->xsize / (double)(im->end - im->start);
115         return im->xorigin;
116     }
117     return (int)((double)im->xorigin 
118                  + pixie * ( mytime - im->start ) );
119 }
120
121 /* translate data values into y coordinates */
122 int
123 ytr(image_desc_t *im, double value){
124     static double pixie;
125     double yval;
126     if (isnan(value)){
127       if(!im->logarithmic)
128         pixie = (double) im->ysize / (im->maxval - im->minval);
129       else 
130         pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
131       yval = im->yorigin;
132     } else if(!im->logarithmic) {
133       yval = im->yorigin - pixie * (value - im->minval) + 0.5;
134     } else {
135       if (value < im->minval) {
136         yval = im->yorigin;
137       } else {
138         yval = im->yorigin - pixie * (log10(value) - log10(im->minval)) + 0.5;
139       }
140     }
141     /* make sure we don't return anything too unreasonable. GD lib can
142        get terribly slow when drawing lines outside its scope. This is 
143        especially problematic in connection with the rigid option */
144     if (! im->rigid) {
145       return (int)yval;
146     } else if ((int)yval > im->yorigin) {
147       return im->yorigin+2;
148     } else if ((int) yval < im->yorigin - im->ysize){
149       return im->yorigin - im->ysize - 2;
150     } else {
151       return (int)yval;
152     } 
153 }
154
155
156
157 /* conversion function for symbolic entry names */
158
159
160 #define conv_if(VV,VVV) \
161    if (strcmp(#VV, string) == 0) return VVV ;
162
163 enum gf_en gf_conv(char *string){
164     
165     conv_if(PRINT,GF_PRINT)
166     conv_if(GPRINT,GF_GPRINT)
167     conv_if(COMMENT,GF_COMMENT)
168     conv_if(HRULE,GF_HRULE)
169     conv_if(VRULE,GF_VRULE)
170     conv_if(LINE,GF_LINE)
171     conv_if(AREA,GF_AREA)
172     conv_if(STACK,GF_STACK)
173     conv_if(TICK,GF_TICK)
174     conv_if(DEF,GF_DEF)
175     conv_if(CDEF,GF_CDEF)
176     conv_if(VDEF,GF_VDEF)
177     conv_if(PART,GF_PART)
178     
179     return (-1);
180 }
181
182 enum if_en if_conv(char *string){
183     
184     conv_if(PNG,IF_PNG)
185
186     return (-1);
187 }
188
189 enum tmt_en tmt_conv(char *string){
190
191     conv_if(SECOND,TMT_SECOND)
192     conv_if(MINUTE,TMT_MINUTE)
193     conv_if(HOUR,TMT_HOUR)
194     conv_if(DAY,TMT_DAY)
195     conv_if(WEEK,TMT_WEEK)
196     conv_if(MONTH,TMT_MONTH)
197     conv_if(YEAR,TMT_YEAR)
198     return (-1);
199 }
200
201 enum grc_en grc_conv(char *string){
202
203     conv_if(BACK,GRC_BACK)
204     conv_if(CANVAS,GRC_CANVAS)
205     conv_if(SHADEA,GRC_SHADEA)
206     conv_if(SHADEB,GRC_SHADEB)
207     conv_if(GRID,GRC_GRID)
208     conv_if(MGRID,GRC_MGRID)
209     conv_if(FONT,GRC_FONT)
210     conv_if(FRAME,GRC_FRAME)
211     conv_if(ARROW,GRC_ARROW)
212
213     return -1;  
214 }
215
216 enum text_prop_en text_prop_conv(char *string){
217       
218     conv_if(DEFAULT,TEXT_PROP_DEFAULT)
219     conv_if(TITLE,TEXT_PROP_TITLE)
220     conv_if(AXIS,TEXT_PROP_AXIS)
221     conv_if(UNIT,TEXT_PROP_UNIT)
222     conv_if(LEGEND,TEXT_PROP_LEGEND)
223     return -1;
224 }
225
226
227 #undef conv_if
228
229
230
231 int
232 im_free(image_desc_t *im)
233 {
234     long i,ii;
235     if (im == NULL) return 0;
236     for(i=0;i<im->gdes_c;i++){
237       if (im->gdes[i].data_first){
238         /* careful here, because a single pointer can occur several times */
239           free (im->gdes[i].data);
240           if (im->gdes[i].ds_namv){
241               for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
242                   free(im->gdes[i].ds_namv[ii]);
243               free(im->gdes[i].ds_namv);
244           }
245       }
246       free (im->gdes[i].p_data);
247       free (im->gdes[i].rpnp);
248     }
249     free(im->gdes);
250     return 0;
251 }
252
253 /* find SI magnitude symbol for the given number*/
254 void
255 auto_scale(
256            image_desc_t *im,   /* image description */
257            double *value,
258            char **symb_ptr,
259            double *magfact
260            )
261 {
262         
263     char *symbol[] = {"a", /* 10e-18 Atto */
264                       "f", /* 10e-15 Femto */
265                       "p", /* 10e-12 Pico */
266                       "n", /* 10e-9  Nano */
267                       "u", /* 10e-6  Micro */
268                       "m", /* 10e-3  Milli */
269                       " ", /* Base */
270                       "k", /* 10e3   Kilo */
271                       "M", /* 10e6   Mega */
272                       "G", /* 10e9   Giga */
273                       "T", /* 10e12  Tera */
274                       "P", /* 10e15  Peta */
275                       "E"};/* 10e18  Exa */
276
277     int symbcenter = 6;
278     int sindex;  
279
280     if (*value == 0.0 || isnan(*value) ) {
281         sindex = 0;
282         *magfact = 1.0;
283     } else {
284         sindex = floor(log(fabs(*value))/log((double)im->base)); 
285         *magfact = pow((double)im->base, (double)sindex);
286         (*value) /= (*magfact);
287     }
288     if ( sindex <= symbcenter && sindex >= -symbcenter) {
289         (*symb_ptr) = symbol[sindex+symbcenter];
290     }
291     else {
292         (*symb_ptr) = "?";
293     }
294 }
295
296
297 /* find SI magnitude symbol for the numbers on the y-axis*/
298 void 
299 si_unit(
300     image_desc_t *im   /* image description */
301 )
302 {
303
304     char symbol[] = {'a', /* 10e-18 Atto */ 
305                      'f', /* 10e-15 Femto */
306                      'p', /* 10e-12 Pico */
307                      'n', /* 10e-9  Nano */
308                      'u', /* 10e-6  Micro */
309                      'm', /* 10e-3  Milli */
310                      ' ', /* Base */
311                      'k', /* 10e3   Kilo */
312                      'M', /* 10e6   Mega */
313                      'G', /* 10e9   Giga */
314                      'T', /* 10e12  Tera */
315                      'P', /* 10e15  Peta */
316                      'E'};/* 10e18  Exa */
317
318     int   symbcenter = 6;
319     double digits;  
320     
321     if (im->unitsexponent != 9999) {
322         /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
323         digits = floor(im->unitsexponent / 3);
324     } else {
325         digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base)); 
326     }
327     im->magfact = pow((double)im->base , digits);
328
329 #ifdef DEBUG
330     printf("digits %6.3f  im->magfact %6.3f\n",digits,im->magfact);
331 #endif
332
333     if ( ((digits+symbcenter) < sizeof(symbol)) &&
334                     ((digits+symbcenter) >= 0) )
335         im->symbol = symbol[(int)digits+symbcenter];
336     else
337         im->symbol = ' ';
338  }
339
340 /*  move min and max values around to become sensible */
341
342 void 
343 expand_range(image_desc_t *im)
344 {
345     double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
346                               600.0,500.0,400.0,300.0,250.0,
347                               200.0,125.0,100.0,90.0,80.0,
348                               75.0,70.0,60.0,50.0,40.0,30.0,
349                               25.0,20.0,10.0,9.0,8.0,
350                               7.0,6.0,5.0,4.0,3.5,3.0,
351                               2.5,2.0,1.8,1.5,1.2,1.0,
352                               0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
353     
354     double scaled_min,scaled_max;  
355     double adj;
356     int i;
357     
358
359     
360 #ifdef DEBUG
361     printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
362            im->minval,im->maxval,im->magfact);
363 #endif
364
365     if (isnan(im->ygridstep)){
366         if(im->extra_flags & ALTAUTOSCALE) {
367             /* measure the amplitude of the function. Make sure that
368                graph boundaries are slightly higher then max/min vals
369                so we can see amplitude on the graph */
370               double delt, fact;
371
372               delt = im->maxval - im->minval;
373               adj = delt * 0.1;
374               fact = 2.0 * pow(10.0,
375                     floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
376               if (delt < fact) {
377                 adj = (fact - delt) * 0.55;
378 #ifdef DEBUG
379               printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
380 #endif
381               }
382               im->minval -= adj;
383               im->maxval += adj;
384         }
385         else if(im->extra_flags & ALTAUTOSCALE_MAX) {
386             /* measure the amplitude of the function. Make sure that
387                graph boundaries are slightly higher than max vals
388                so we can see amplitude on the graph */
389               adj = (im->maxval - im->minval) * 0.1;
390               im->maxval += adj;
391         }
392         else {
393             scaled_min = im->minval / im->magfact;
394             scaled_max = im->maxval / im->magfact;
395             
396             for (i=1; sensiblevalues[i] > 0; i++){
397                 if (sensiblevalues[i-1]>=scaled_min &&
398                     sensiblevalues[i]<=scaled_min)      
399                     im->minval = sensiblevalues[i]*(im->magfact);
400                 
401                 if (-sensiblevalues[i-1]<=scaled_min &&
402                 -sensiblevalues[i]>=scaled_min)
403                     im->minval = -sensiblevalues[i-1]*(im->magfact);
404                 
405                 if (sensiblevalues[i-1] >= scaled_max &&
406                     sensiblevalues[i] <= scaled_max)
407                     im->maxval = sensiblevalues[i-1]*(im->magfact);
408                 
409                 if (-sensiblevalues[i-1]<=scaled_max &&
410                     -sensiblevalues[i] >=scaled_max)
411                     im->maxval = -sensiblevalues[i]*(im->magfact);
412             }
413         }
414     } else {
415         /* adjust min and max to the grid definition if there is one */
416         im->minval = (double)im->ylabfact * im->ygridstep * 
417             floor(im->minval / ((double)im->ylabfact * im->ygridstep));
418         im->maxval = (double)im->ylabfact * im->ygridstep * 
419             ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
420     }
421     
422 #ifdef DEBUG
423     fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
424            im->minval,im->maxval,im->magfact);
425 #endif
426 }
427
428     
429 /* reduce data reimplementation by Alex */
430
431 void
432 reduce_data(
433     enum cf_en     cf,         /* which consolidation function ?*/
434     unsigned long  cur_step,   /* step the data currently is in */
435     time_t         *start,     /* start, end and step as requested ... */
436     time_t         *end,       /* ... by the application will be   ... */
437     unsigned long  *step,      /* ... adjusted to represent reality    */
438     unsigned long  *ds_cnt,    /* number of data sources in file */
439     rrd_value_t    **data)     /* two dimensional array containing the data */
440 {
441     int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
442     unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
443     rrd_value_t    *srcptr,*dstptr;
444
445     (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
446     dstptr = *data;
447     srcptr = *data;
448     row_cnt = ((*end)-(*start))/cur_step;
449
450 #ifdef DEBUG
451 #define DEBUG_REDUCE
452 #endif
453 #ifdef DEBUG_REDUCE
454 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
455                         row_cnt,reduce_factor,*start,*end,cur_step);
456 for (col=0;col<row_cnt;col++) {
457     printf("time %10lu: ",*start+(col+1)*cur_step);
458     for (i=0;i<*ds_cnt;i++)
459         printf(" %8.2e",srcptr[*ds_cnt*col+i]);
460     printf("\n");
461 }
462 #endif
463
464     /* We have to combine [reduce_factor] rows of the source
465     ** into one row for the destination.  Doing this we also
466     ** need to take care to combine the correct rows.  First
467     ** alter the start and end time so that they are multiples
468     ** of the new step time.  We cannot reduce the amount of
469     ** time so we have to move the end towards the future and
470     ** the start towards the past.
471     */
472     end_offset = (*end) % (*step);
473     start_offset = (*start) % (*step);
474
475     /* If there is a start offset (which cannot be more than
476     ** one destination row), skip the appropriate number of
477     ** source rows and one destination row.  The appropriate
478     ** number is what we do know (start_offset/cur_step) of
479     ** the new interval (*step/cur_step aka reduce_factor).
480     */
481 #ifdef DEBUG_REDUCE
482 printf("start_offset: %lu  end_offset: %lu\n",start_offset,end_offset);
483 printf("row_cnt before:  %lu\n",row_cnt);
484 #endif
485     if (start_offset) {
486         (*start) = (*start)-start_offset;
487         skiprows=reduce_factor-start_offset/cur_step;
488         srcptr+=skiprows* *ds_cnt;
489         for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
490         row_cnt-=skiprows;
491     }
492 #ifdef DEBUG_REDUCE
493 printf("row_cnt between: %lu\n",row_cnt);
494 #endif
495
496     /* At the end we have some rows that are not going to be
497     ** used, the amount is end_offset/cur_step
498     */
499     if (end_offset) {
500         (*end) = (*end)-end_offset+(*step);
501         skiprows = end_offset/cur_step;
502         row_cnt-=skiprows;
503     }
504 #ifdef DEBUG_REDUCE
505 printf("row_cnt after:   %lu\n",row_cnt);
506 #endif
507
508 /* Sanity check: row_cnt should be multiple of reduce_factor */
509 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
510
511     if (row_cnt%reduce_factor) {
512         printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
513                                 row_cnt,reduce_factor);
514         printf("BUG in reduce_data()\n");
515         exit(1);
516     }
517
518     /* Now combine reduce_factor intervals at a time
519     ** into one interval for the destination.
520     */
521
522     for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
523         for (col=0;col<(*ds_cnt);col++) {
524             rrd_value_t newval=DNAN;
525             unsigned long validval=0;
526
527             for (i=0;i<reduce_factor;i++) {
528                 if (isnan(srcptr[i*(*ds_cnt)+col])) {
529                     continue;
530                 }
531                 validval++;
532                 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
533                 else {
534                     switch (cf) {
535                         case CF_HWPREDICT:
536                         case CF_DEVSEASONAL:
537                         case CF_DEVPREDICT:
538                         case CF_SEASONAL:
539                         case CF_AVERAGE:
540                             newval += srcptr[i*(*ds_cnt)+col];
541                             break;
542                         case CF_MINIMUM:
543                             newval = min (newval,srcptr[i*(*ds_cnt)+col]);
544                             break;
545                         case CF_FAILURES: 
546                         /* an interval contains a failure if any subintervals contained a failure */
547                         case CF_MAXIMUM:
548                             newval = max (newval,srcptr[i*(*ds_cnt)+col]);
549                             break;
550                         case CF_LAST:
551                             newval = srcptr[i*(*ds_cnt)+col];
552                             break;
553                     }
554                 }
555             }
556             if (validval == 0){newval = DNAN;} else{
557                 switch (cf) {
558                     case CF_HWPREDICT:
559             case CF_DEVSEASONAL:
560                     case CF_DEVPREDICT:
561                     case CF_SEASONAL:
562                     case CF_AVERAGE:                
563                        newval /= validval;
564                         break;
565                     case CF_MINIMUM:
566                     case CF_FAILURES:
567                     case CF_MAXIMUM:
568                     case CF_LAST:
569                         break;
570                 }
571             }
572             *dstptr++=newval;
573         }
574         srcptr+=(*ds_cnt)*reduce_factor;
575         row_cnt-=reduce_factor;
576     }
577     /* If we had to alter the endtime, we didn't have enough
578     ** source rows to fill the last row. Fill it with NaN.
579     */
580     if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
581 #ifdef DEBUG_REDUCE
582     row_cnt = ((*end)-(*start))/ *step;
583     srcptr = *data;
584     printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
585                                 row_cnt,*start,*end,*step);
586 for (col=0;col<row_cnt;col++) {
587     printf("time %10lu: ",*start+(col+1)*(*step));
588     for (i=0;i<*ds_cnt;i++)
589         printf(" %8.2e",srcptr[*ds_cnt*col+i]);
590     printf("\n");
591 }
592 #endif
593 }
594
595
596 /* get the data required for the graphs from the 
597    relevant rrds ... */
598
599 int
600 data_fetch( image_desc_t *im )
601 {
602     int       i,ii;
603     int skip;
604     /* pull the data from the log files ... */
605     for (i=0;i<im->gdes_c;i++){
606         /* only GF_DEF elements fetch data */
607         if (im->gdes[i].gf != GF_DEF) 
608             continue;
609
610         skip=0;
611         /* do we have it already ?*/
612         for (ii=0;ii<i;ii++){
613             if (im->gdes[ii].gf != GF_DEF) 
614                 continue;
615             if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
616                 && (im->gdes[i].cf == im->gdes[ii].cf)){
617                 /* OK the data it is here already ... 
618                  * we just copy the header portion */
619                 im->gdes[i].start = im->gdes[ii].start;
620                 im->gdes[i].end = im->gdes[ii].end;
621                 im->gdes[i].step = im->gdes[ii].step;
622                 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
623                 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;             
624                 im->gdes[i].data = im->gdes[ii].data;
625                 im->gdes[i].data_first = 0;
626                 skip=1;
627             }
628             if (skip) 
629                 break;
630         }
631         if (! skip) {
632             unsigned long  ft_step = im->gdes[i].step ;
633             
634             if((rrd_fetch_fn(im->gdes[i].rrd,
635                              im->gdes[i].cf,
636                              &im->gdes[i].start,
637                              &im->gdes[i].end,
638                              &ft_step,
639                              &im->gdes[i].ds_cnt,
640                              &im->gdes[i].ds_namv,
641                              &im->gdes[i].data)) == -1){                
642                 return -1;
643             }
644             im->gdes[i].data_first = 1;     
645         
646             if (ft_step < im->gdes[i].step) {
647                 reduce_data(im->gdes[i].cf,
648                             ft_step,
649                             &im->gdes[i].start,
650                             &im->gdes[i].end,
651                             &im->gdes[i].step,
652                             &im->gdes[i].ds_cnt,
653                             &im->gdes[i].data);
654             } else {
655                 im->gdes[i].step = ft_step;
656             }
657         }
658         
659         /* lets see if the required data source is realy there */
660         for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
661             if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
662                 im->gdes[i].ds=ii; }
663         }
664         if (im->gdes[i].ds== -1){
665             rrd_set_error("No DS called '%s' in '%s'",
666                           im->gdes[i].ds_nam,im->gdes[i].rrd);
667             return -1; 
668         }
669         
670     }
671     return 0;
672 }
673
674 /* evaluate the expressions in the CDEF functions */
675
676 /*************************************************************
677  * CDEF stuff 
678  *************************************************************/
679
680 long
681 find_var_wrapper(void *arg1, char *key)
682 {
683    return find_var((image_desc_t *) arg1, key);
684 }
685
686 /* find gdes containing var*/
687 long
688 find_var(image_desc_t *im, char *key){
689     long ii;
690     for(ii=0;ii<im->gdes_c-1;ii++){
691         if((im->gdes[ii].gf == GF_DEF 
692             || im->gdes[ii].gf == GF_VDEF
693             || im->gdes[ii].gf == GF_CDEF) 
694            && (strcmp(im->gdes[ii].vname,key) == 0)){
695             return ii; 
696         }          
697     }               
698     return -1;
699 }
700
701 /* find the largest common denominator for all the numbers
702    in the 0 terminated num array */
703 long
704 lcd(long *num){
705     long rest;
706     int i;
707     for (i=0;num[i+1]!=0;i++){
708         do { 
709             rest=num[i] % num[i+1];
710             num[i]=num[i+1]; num[i+1]=rest;
711         } while (rest!=0);
712         num[i+1] = num[i];
713     }
714 /*    return i==0?num[i]:num[i-1]; */
715       return num[i];
716 }
717
718 /* run the rpn calculator on all the VDEF and CDEF arguments */
719 int
720 data_calc( image_desc_t *im){
721
722     int       gdi;
723     int       dataidx;
724     long      *steparray, rpi;
725     int       stepcnt;
726     time_t    now;
727     rpnstack_t rpnstack;
728
729     rpnstack_init(&rpnstack);
730
731     for (gdi=0;gdi<im->gdes_c;gdi++){
732         /* Look for GF_VDEF and GF_CDEF in the same loop,
733          * so CDEFs can use VDEFs and vice versa
734          */
735         switch (im->gdes[gdi].gf) {
736             case GF_VDEF:
737                 /* A VDEF has no DS.  This also signals other parts
738                  * of rrdtool that this is a VDEF value, not a CDEF.
739                  */
740                 im->gdes[gdi].ds_cnt = 0;
741                 if (vdef_calc(im,gdi)) {
742                     rrd_set_error("Error processing VDEF '%s'"
743                         ,im->gdes[gdi].vname
744                         );
745                     rpnstack_free(&rpnstack);
746                     return -1;
747                 }
748                 break;
749             case GF_CDEF:
750                 im->gdes[gdi].ds_cnt = 1;
751                 im->gdes[gdi].ds = 0;
752                 im->gdes[gdi].data_first = 1;
753                 im->gdes[gdi].start = 0;
754                 im->gdes[gdi].end = 0;
755                 steparray=NULL;
756                 stepcnt = 0;
757                 dataidx=-1;
758
759                 /* Find the variables in the expression.
760                  * - VDEF variables are substituted by their values
761                  *   and the opcode is changed into OP_NUMBER.
762                  * - CDEF variables are analized for their step size,
763                  *   the lowest common denominator of all the step
764                  *   sizes of the data sources involved is calculated
765                  *   and the resulting number is the step size for the
766                  *   resulting data source.
767                  */
768                 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
769                     if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
770                         long ptr = im->gdes[gdi].rpnp[rpi].ptr;
771                         if (im->gdes[ptr].ds_cnt == 0) {
772 #if 0
773 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
774         im->gdes[gdi].vname,
775         im->gdes[ptr].vname);
776 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
777 #endif
778                             im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
779                             im->gdes[gdi].rpnp[rpi].op  = OP_NUMBER;
780                         } else {
781                             if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
782                                 rrd_set_error("realloc steparray");
783                                 rpnstack_free(&rpnstack);
784                                 return -1;
785                             };
786
787                             steparray[stepcnt-1] = im->gdes[ptr].step;
788
789                             /* adjust start and end of cdef (gdi) so
790                              * that it runs from the latest start point
791                              * to the earliest endpoint of any of the
792                              * rras involved (ptr)
793                              */
794                             if(im->gdes[gdi].start < im->gdes[ptr].start)
795                                 im->gdes[gdi].start = im->gdes[ptr].start;
796
797                             if(im->gdes[gdi].end == 0 ||
798                                         im->gdes[gdi].end > im->gdes[ptr].end)
799                                 im->gdes[gdi].end = im->gdes[ptr].end;
800                 
801                             /* store pointer to the first element of
802                              * the rra providing data for variable,
803                              * further save step size and data source
804                              * count of this rra
805                              */ 
806                             im->gdes[gdi].rpnp[rpi].data =  im->gdes[ptr].data + im->gdes[ptr].ds;
807                             im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
808                             im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
809
810                             /* backoff the *.data ptr; this is done so
811                              * rpncalc() function doesn't have to treat
812                              * the first case differently
813                              */
814                         } /* if ds_cnt != 0 */
815                     } /* if OP_VARIABLE */
816                 } /* loop through all rpi */
817
818                 /* move the data pointers to the correct period */
819                 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
820                     if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
821                         long ptr = im->gdes[gdi].rpnp[rpi].ptr;
822                         if(im->gdes[gdi].start > im->gdes[ptr].start) {
823                             im->gdes[gdi].rpnp[rpi].data += im->gdes[gdi].rpnp[rpi].ds_cnt;
824                         }
825                      }
826                 }
827         
828
829                 if(steparray == NULL){
830                     rrd_set_error("rpn expressions without DEF"
831                                 " or CDEF variables are not supported");
832                     rpnstack_free(&rpnstack);
833                     return -1;    
834                 }
835                 steparray[stepcnt]=0;
836                 /* Now find the resulting step.  All steps in all
837                  * used RRAs have to be visited
838                  */
839                 im->gdes[gdi].step = lcd(steparray);
840                 free(steparray);
841                 if((im->gdes[gdi].data = malloc((
842                                 (im->gdes[gdi].end-im->gdes[gdi].start) 
843                                     / im->gdes[gdi].step)
844                                     * sizeof(double)))==NULL){
845                     rrd_set_error("malloc im->gdes[gdi].data");
846                     rpnstack_free(&rpnstack);
847                     return -1;
848                 }
849         
850                 /* Step through the new cdef results array and
851                  * calculate the values
852                  */
853                 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
854                                 now<=im->gdes[gdi].end;
855                                 now += im->gdes[gdi].step)
856                 {
857                     rpnp_t  *rpnp = im -> gdes[gdi].rpnp;
858
859                     /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
860                      * in this case we are advancing by timesteps;
861                      * we use the fact that time_t is a synonym for long
862                      */
863                     if (rpn_calc(rpnp,&rpnstack,(long) now, 
864                                 im->gdes[gdi].data,++dataidx) == -1) {
865                         /* rpn_calc sets the error string */
866                         rpnstack_free(&rpnstack); 
867                         return -1;
868                     } 
869                 } /* enumerate over time steps within a CDEF */
870                 break;
871             default:
872                 continue;
873         }
874     } /* enumerate over CDEFs */
875     rpnstack_free(&rpnstack);
876     return 0;
877 }
878
879 /* massage data so, that we get one value for each x coordinate in the graph */
880 int
881 data_proc( image_desc_t *im ){
882     long i,ii;
883     double pixstep = (double)(im->end-im->start)
884         /(double)im->xsize; /* how much time 
885                                passes in one pixel */
886     double paintval;
887     double minval=DNAN,maxval=DNAN;
888     
889     unsigned long gr_time;    
890
891     /* memory for the processed data */
892     for(i=0;i<im->gdes_c;i++){
893       if((im->gdes[i].gf==GF_LINE) ||
894          (im->gdes[i].gf==GF_AREA) ||
895          (im->gdes[i].gf==GF_TICK) ||
896          (im->gdes[i].gf==GF_STACK)){
897         if((im->gdes[i].p_data = malloc((im->xsize +1)
898                                         * sizeof(rrd_value_t)))==NULL){
899           rrd_set_error("malloc data_proc");
900           return -1;
901         }
902       }
903     }
904     
905     for(i=0;i<im->xsize;i++){
906         long vidx;
907         gr_time = im->start+pixstep*i; /* time of the 
908                                           current step */
909         paintval=0.0;
910         
911         for(ii=0;ii<im->gdes_c;ii++){
912           double value;
913             switch(im->gdes[ii].gf){
914             case GF_LINE:
915             case GF_AREA:
916                 case GF_TICK:
917                 paintval = 0.0;
918             case GF_STACK:
919                 vidx = im->gdes[ii].vidx;
920
921                 value =
922                     im->gdes[vidx].data[
923                         ((unsigned long)floor(
924         (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
925                                              )
926                         )       *im->gdes[vidx].ds_cnt
927                                 +im->gdes[vidx].ds];
928
929                 if (! isnan(value)) {
930                   paintval += value;
931                   im->gdes[ii].p_data[i] = paintval;
932                   /* GF_TICK: the data values are not relevant for min and max */
933                   if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
934                    if (isnan(minval) || paintval <  minval)
935                      minval = paintval;
936                    if (isnan(maxval) || paintval >  maxval)
937                      maxval = paintval;
938                   }
939                 } else {
940                   im->gdes[ii].p_data[i] = DNAN;
941                 }
942                 break;
943             case GF_PRINT:
944             case GF_GPRINT:
945             case GF_COMMENT:
946             case GF_HRULE:
947             case GF_VRULE:
948             case GF_DEF:               
949             case GF_CDEF:
950             case GF_VDEF:
951             case GF_PART:
952                 break;
953             }
954         }
955     }
956
957     /* if min or max have not been asigned a value this is because
958        there was no data in the graph ... this is not good ...
959        lets set these to dummy values then ... */
960
961     if (isnan(minval)) minval = 0.0;
962     if (isnan(maxval)) maxval = 1.0;
963     
964     /* adjust min and max values */
965     if (isnan(im->minval) 
966         || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
967             && im->minval > minval))
968         im->minval = minval;
969     if (isnan(im->maxval) 
970         || (!im->rigid 
971             && im->maxval < maxval)){
972         if (im->logarithmic)
973             im->maxval = maxval * 1.1;
974         else
975             im->maxval = maxval;
976     }
977     /* make sure min and max are not equal */
978     if (im->minval == im->maxval) {
979       im->maxval *= 1.01; 
980       if (! im->logarithmic) {
981         im->minval *= 0.99;
982       }
983       
984       /* make sure min and max are not both zero */
985       if (im->maxval == 0.0) {
986             im->maxval = 1.0;
987       }
988         
989     }
990     return 0;
991 }
992
993
994
995 /* identify the point where the first gridline, label ... gets placed */
996
997 time_t
998 find_first_time(
999     time_t   start, /* what is the initial time */
1000     enum tmt_en baseint,  /* what is the basic interval */
1001     long     basestep /* how many if these do we jump a time */
1002     )
1003 {
1004     struct tm tm;
1005     tm = *localtime(&start);
1006     switch(baseint){
1007     case TMT_SECOND:
1008         tm.tm_sec -= tm.tm_sec % basestep; break;
1009     case TMT_MINUTE: 
1010         tm.tm_sec=0;
1011         tm.tm_min -= tm.tm_min % basestep; 
1012         break;
1013     case TMT_HOUR:
1014         tm.tm_sec=0;
1015         tm.tm_min = 0;
1016         tm.tm_hour -= tm.tm_hour % basestep; break;
1017     case TMT_DAY:
1018         /* we do NOT look at the basestep for this ... */
1019         tm.tm_sec=0;
1020         tm.tm_min = 0;
1021         tm.tm_hour = 0; break;
1022     case TMT_WEEK:
1023         /* we do NOT look at the basestep for this ... */
1024         tm.tm_sec=0;
1025         tm.tm_min = 0;
1026         tm.tm_hour = 0;
1027         tm.tm_mday -= tm.tm_wday -1;    /* -1 because we want the monday */
1028         if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1029         break;
1030     case TMT_MONTH:
1031         tm.tm_sec=0;
1032         tm.tm_min = 0;
1033         tm.tm_hour = 0;
1034         tm.tm_mday = 1;
1035         tm.tm_mon -= tm.tm_mon % basestep; break;
1036
1037     case TMT_YEAR:
1038         tm.tm_sec=0;
1039         tm.tm_min = 0;
1040         tm.tm_hour = 0;
1041         tm.tm_mday = 1;
1042         tm.tm_mon = 0;
1043         tm.tm_year -= (tm.tm_year+1900) % basestep;
1044         
1045     }
1046     return mktime(&tm);
1047 }
1048 /* identify the point where the next gridline, label ... gets placed */
1049 time_t 
1050 find_next_time(
1051     time_t   current, /* what is the initial time */
1052     enum tmt_en baseint,  /* what is the basic interval */
1053     long     basestep /* how many if these do we jump a time */
1054     )
1055 {
1056     struct tm tm;
1057     time_t madetime;
1058     tm = *localtime(&current);
1059     do {
1060         switch(baseint){
1061         case TMT_SECOND:
1062             tm.tm_sec += basestep; break;
1063         case TMT_MINUTE: 
1064             tm.tm_min += basestep; break;
1065         case TMT_HOUR:
1066             tm.tm_hour += basestep; break;
1067         case TMT_DAY:
1068             tm.tm_mday += basestep; break;
1069         case TMT_WEEK:
1070             tm.tm_mday += 7*basestep; break;
1071         case TMT_MONTH:
1072             tm.tm_mon += basestep; break;
1073         case TMT_YEAR:
1074             tm.tm_year += basestep;     
1075         }
1076         madetime = mktime(&tm);
1077     } while (madetime == -1); /* this is necessary to skip impssible times
1078                                  like the daylight saving time skips */
1079     return madetime;
1080           
1081 }
1082
1083
1084 /* calculate values required for PRINT and GPRINT functions */
1085
1086 int
1087 print_calc(image_desc_t *im, char ***prdata) 
1088 {
1089     long i,ii,validsteps;
1090     double printval;
1091     time_t printtime;
1092     int graphelement = 0;
1093     long vidx;
1094     int max_ii; 
1095     double magfact = -1;
1096     char *si_symb = "";
1097     char *percent_s;
1098     int prlines = 1;
1099     if (im->imginfo) prlines++;
1100     for(i=0;i<im->gdes_c;i++){
1101         switch(im->gdes[i].gf){
1102         case GF_PRINT:
1103             prlines++;
1104             if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1105                 rrd_set_error("realloc prdata");
1106                 return 0;
1107             }
1108         case GF_GPRINT:
1109             /* PRINT and GPRINT can now print VDEF generated values.
1110              * There's no need to do any calculations on them as these
1111              * calculations were already made.
1112              */
1113             vidx = im->gdes[i].vidx;
1114             if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1115                 printval = im->gdes[vidx].vf.val;
1116                 printtime = im->gdes[vidx].vf.when;
1117             } else { /* need to calculate max,min,avg etcetera */
1118                 max_ii =((im->gdes[vidx].end 
1119                         - im->gdes[vidx].start)
1120                         / im->gdes[vidx].step
1121                         * im->gdes[vidx].ds_cnt);
1122                 printval = DNAN;
1123                 validsteps = 0;
1124                 for(    ii=im->gdes[vidx].ds;
1125                         ii < max_ii;
1126                         ii+=im->gdes[vidx].ds_cnt){
1127                     if (! finite(im->gdes[vidx].data[ii]))
1128                         continue;
1129                     if (isnan(printval)){
1130                         printval = im->gdes[vidx].data[ii];
1131                         validsteps++;
1132                         continue;
1133                     }
1134
1135                     switch (im->gdes[i].cf){
1136                         case CF_HWPREDICT:
1137                         case CF_DEVPREDICT:
1138                         case CF_DEVSEASONAL:
1139                         case CF_SEASONAL:
1140                         case CF_AVERAGE:
1141                             validsteps++;
1142                             printval += im->gdes[vidx].data[ii];
1143                             break;
1144                         case CF_MINIMUM:
1145                             printval = min( printval, im->gdes[vidx].data[ii]);
1146                             break;
1147                         case CF_FAILURES:
1148                         case CF_MAXIMUM:
1149                             printval = max( printval, im->gdes[vidx].data[ii]);
1150                             break;
1151                         case CF_LAST:
1152                             printval = im->gdes[vidx].data[ii];
1153                     }
1154                 }
1155                 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1156                     if (validsteps > 1) {
1157                         printval = (printval / validsteps);
1158                     }
1159                 }
1160             } /* prepare printval */
1161
1162             if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1163                 if (im->gdes[i].gf == GF_PRINT){
1164                     (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1165                     sprintf((*prdata)[prlines-2],"%s (%lu)",
1166                                         ctime(&printtime),printtime);
1167                     (*prdata)[prlines-1] = NULL;
1168                 } else {
1169                     sprintf(im->gdes[i].legend,"%s (%lu)",
1170                                         ctime(&printtime),printtime);
1171                     graphelement = 1;
1172                 }
1173             } else {
1174             if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1175                 /* Magfact is set to -1 upon entry to print_calc.  If it
1176                  * is still less than 0, then we need to run auto_scale.
1177                  * Otherwise, put the value into the correct units.  If
1178                  * the value is 0, then do not set the symbol or magnification
1179                  * so next the calculation will be performed again. */
1180                 if (magfact < 0.0) {
1181                     auto_scale(im,&printval,&si_symb,&magfact);
1182                     if (printval == 0.0)
1183                         magfact = -1.0;
1184                 } else {
1185                     printval /= magfact;
1186                 }
1187                 *(++percent_s) = 's';
1188             } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1189                 auto_scale(im,&printval,&si_symb,&magfact);
1190             }
1191
1192             if (im->gdes[i].gf == GF_PRINT){
1193                 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1194                 if (bad_format(im->gdes[i].format)) {
1195                         rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1196                         return -1;
1197                 }
1198 #ifdef HAVE_SNPRINTF
1199                 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1200 #else
1201                 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1202 #endif
1203                 (*prdata)[prlines-1] = NULL;
1204             } else {
1205                 /* GF_GPRINT */
1206
1207                 if (bad_format(im->gdes[i].format)) {
1208                         rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1209                         return -1;
1210                 }
1211 #ifdef HAVE_SNPRINTF
1212                 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1213 #else
1214                 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1215 #endif
1216                 graphelement = 1;
1217             }
1218             }
1219             break;
1220         case GF_COMMENT:
1221         case GF_LINE:
1222         case GF_AREA:
1223         case GF_TICK:
1224         case GF_STACK:
1225         case GF_HRULE:
1226         case GF_VRULE:
1227             graphelement = 1;
1228             break;
1229         case GF_DEF:
1230         case GF_CDEF:       
1231         case GF_VDEF:       
1232         case GF_PART:
1233             break;
1234         }
1235     }
1236     return graphelement;
1237 }
1238
1239
1240 /* place legends with color spots */
1241 int
1242 leg_place(image_desc_t *im)
1243 {
1244     /* graph labels */
1245     int   interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1246     int   box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1247     int   border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1248     int   fill=0, fill_last;
1249     int   leg_c = 0;
1250     int   leg_x = border, leg_y = im->ygif;
1251     int   leg_cc;
1252     int   glue = 0;
1253     int   i,ii, mark = 0;
1254     char  prt_fctn; /*special printfunctions */
1255     int  *legspace;
1256
1257   if( !(im->extra_flags & NOLEGEND) ) {
1258     if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1259        rrd_set_error("malloc for legspace");
1260        return -1;
1261     }
1262
1263     for(i=0;i<im->gdes_c;i++){
1264         fill_last = fill;
1265
1266         leg_cc = strlen(im->gdes[i].legend);
1267         
1268         /* is there a controle code ant the end of the legend string ? */ 
1269         if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1270             prt_fctn = im->gdes[i].legend[leg_cc-1];
1271             leg_cc -= 2;
1272             im->gdes[i].legend[leg_cc] = '\0';
1273         } else {
1274             prt_fctn = '\0';
1275         }
1276         /* remove exess space */
1277         while (prt_fctn=='g' && 
1278                leg_cc > 0 && 
1279                im->gdes[i].legend[leg_cc-1]==' '){
1280            leg_cc--;
1281            im->gdes[i].legend[leg_cc]='\0';
1282         }
1283         if (leg_cc != 0 ){
1284            legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1285            
1286            if (fill > 0){ 
1287                /* no interleg space if string ends in \g */
1288                fill += legspace[i];
1289             }
1290             if (im->gdes[i].gf != GF_GPRINT && 
1291                 im->gdes[i].gf != GF_COMMENT) { 
1292                 fill += box;       
1293             }
1294            fill += gfx_get_text_width(fill+border,im->text_prop[TEXT_PROP_LEGEND].font,
1295                                       im->text_prop[TEXT_PROP_LEGEND].size,
1296                                       im->tabwidth,
1297                                       im->gdes[i].legend);
1298             leg_c++;
1299         } else {
1300            legspace[i]=0;
1301         }
1302         /* who said there was a special tag ... ?*/
1303         if (prt_fctn=='g') {    
1304            prt_fctn = '\0';
1305         }
1306         if (prt_fctn == '\0') {
1307             if (i == im->gdes_c -1 ) prt_fctn ='l';
1308             
1309             /* is it time to place the legends ? */
1310             if (fill > im->xgif - 2*border){
1311                 if (leg_c > 1) {
1312                     /* go back one */
1313                     i--; 
1314                     fill = fill_last;
1315                     leg_c--;
1316                     prt_fctn = 'j';
1317                 } else {
1318                     prt_fctn = 'l';
1319                 }
1320                 
1321             }
1322         }
1323
1324
1325         if (prt_fctn != '\0'){
1326             leg_x = border;
1327             if (leg_c >= 2 && prt_fctn == 'j') {
1328                 glue = (im->xgif - fill - 2* border) / (leg_c-1);
1329             } else {
1330                 glue = 0;
1331             }
1332             if (prt_fctn =='c') leg_x =  (im->xgif - fill) / 2.0;
1333             if (prt_fctn =='r') leg_x =  im->xgif - fill - border;
1334
1335             for(ii=mark;ii<=i;ii++){
1336                 if(im->gdes[ii].legend[0]=='\0')
1337                     continue;
1338                 im->gdes[ii].leg_x = leg_x;
1339                 im->gdes[ii].leg_y = leg_y;
1340                 leg_x += 
1341                  gfx_get_text_width(leg_x,im->text_prop[TEXT_PROP_LEGEND].font,
1342                                       im->text_prop[TEXT_PROP_LEGEND].size,
1343                                       im->tabwidth,
1344                                       im->gdes[ii].legend) 
1345                    + legspace[ii]
1346                    + glue;
1347                 if (im->gdes[ii].gf != GF_GPRINT && 
1348                     im->gdes[ii].gf != GF_COMMENT) 
1349                     leg_x += box;          
1350             }       
1351             leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1352             if (prt_fctn == 's') leg_y -=  im->text_prop[TEXT_PROP_LEGEND].size*1.2;       
1353             fill = 0;
1354             leg_c = 0;
1355             mark = ii;
1356         }          
1357     }
1358     im->ygif = leg_y;
1359     free(legspace);
1360   }
1361   return 0;
1362 }
1363
1364 /* create a grid on the graph. it determines what to do
1365    from the values of xsize, start and end */
1366
1367 /* the xaxis labels are determined from the number of seconds per pixel
1368    in the requested graph */
1369
1370
1371
1372 int
1373 horizontal_grid(gfx_canvas_t *canvas, image_desc_t   *im)
1374 {
1375     double   range;
1376     double   scaledrange;
1377     int      pixel,i;
1378     int      sgrid,egrid;
1379     double   gridstep;
1380     double   scaledstep;
1381     char     graph_label[100];
1382     double   x0,x1,y0,y1;
1383     int      labfact,gridind;
1384     int      decimals, fractionals;
1385     char     labfmt[64];
1386
1387     labfact=2;
1388     gridind=-1;
1389     range =  im->maxval - im->minval;
1390     scaledrange = range / im->magfact;
1391
1392         /* does the scale of this graph make it impossible to put lines
1393            on it? If so, give up. */
1394         if (isnan(scaledrange)) {
1395                 return 0;
1396         }
1397
1398     /* find grid spaceing */
1399     pixel=1;
1400     if(isnan(im->ygridstep)){
1401         if(im->extra_flags & ALTYGRID) {
1402             /* find the value with max number of digits. Get number of digits */
1403             decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1404             if(decimals <= 0) /* everything is small. make place for zero */
1405                 decimals = 1;
1406             
1407             fractionals = floor(log10(range));
1408             if(fractionals < 0) /* small amplitude. */
1409                 sprintf(labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1410             else
1411                 sprintf(labfmt, "%%%d.1f", decimals + 1);
1412             gridstep = pow((double)10, (double)fractionals);
1413             if(gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1414                 gridstep = 0.1;
1415             /* should have at least 5 lines but no more then 15 */
1416             if(range/gridstep < 5)
1417                 gridstep /= 10;
1418             if(range/gridstep > 15)
1419                 gridstep *= 10;
1420             if(range/gridstep > 5) {
1421                 labfact = 1;
1422                 if(range/gridstep > 8)
1423                     labfact = 2;
1424             }
1425             else {
1426                 gridstep /= 5;
1427                 labfact = 5;
1428             }
1429         }
1430         else {
1431             for(i=0;ylab[i].grid > 0;i++){
1432                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1433                 if (gridind == -1 && pixel > 5) {
1434                     gridind = i;
1435                     break;
1436                 }
1437             }
1438             
1439             for(i=0; i<4;i++) {
1440                if (pixel * ylab[gridind].lfac[i] >=  2 * im->text_prop[TEXT_PROP_AXIS].size) {
1441                   labfact =  ylab[gridind].lfac[i];
1442                   break;
1443                }                          
1444             } 
1445             
1446             gridstep = ylab[gridind].grid * im->magfact;
1447         }
1448     } else {
1449         gridstep = im->ygridstep;
1450         labfact = im->ylabfact;
1451     }
1452     
1453    x0=im->xorigin;
1454    x1=im->xorigin+im->xsize;
1455    
1456     sgrid = (int)( im->minval / gridstep - 1);
1457     egrid = (int)( im->maxval / gridstep + 1);
1458     scaledstep = gridstep/im->magfact;
1459     for (i = sgrid; i <= egrid; i++){
1460        y0=ytr(im,gridstep*i);
1461        if ( y0 >= im->yorigin-im->ysize
1462                  && y0 <= im->yorigin){       
1463             if(i % labfact == 0){               
1464                 if (i==0 || im->symbol == ' ') {
1465                     if(scaledstep < 1){
1466                         if(im->extra_flags & ALTYGRID) {
1467                             sprintf(graph_label,labfmt,scaledstep*i);
1468                         }
1469                         else {
1470                             sprintf(graph_label,"%4.1f",scaledstep*i);
1471                         }
1472                     } else {
1473                         sprintf(graph_label,"%4.0f",scaledstep*i);
1474                     }
1475                 }else {
1476                     if(scaledstep < 1){
1477                         sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1478                     } else {
1479                         sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1480                     }
1481                 }
1482
1483                gfx_new_text ( canvas,
1484                               x0-im->text_prop[TEXT_PROP_AXIS].size/1.5, y0,
1485                               im->graph_col[GRC_FONT],
1486                               im->text_prop[TEXT_PROP_AXIS].font,
1487                               im->text_prop[TEXT_PROP_AXIS].size,
1488                               im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1489                               graph_label );
1490                gfx_new_line ( canvas,
1491                               x0-2,y0,
1492                               x1+2,y0,
1493                               MGRIDWIDTH, im->graph_col[GRC_MGRID] );          
1494                
1495             } else {            
1496                gfx_new_line ( canvas,
1497                               x0-1,y0,
1498                               x1+1,y0,
1499                               GRIDWIDTH, im->graph_col[GRC_GRID] );            
1500                
1501             }       
1502         }       
1503     } 
1504     return 1;
1505 }
1506
1507 /* logaritmic horizontal grid */
1508 int
1509 horizontal_log_grid(gfx_canvas_t *canvas, image_desc_t   *im)   
1510 {
1511     double   pixpex;
1512     int      ii,i;
1513     int      minoridx=0, majoridx=0;
1514     char     graph_label[100];
1515     double   x0,x1,y0,y1;   
1516     double   value, pixperstep, minstep;
1517
1518     /* find grid spaceing */
1519     pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1520
1521         if (isnan(pixpex)) {
1522                 return 0;
1523         }
1524
1525     for(i=0;yloglab[i][0] > 0;i++){
1526         minstep = log10(yloglab[i][0]);
1527         for(ii=1;yloglab[i][ii+1] > 0;ii++){
1528             if(yloglab[i][ii+2]==0){
1529                 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1530                 break;
1531             }
1532         }
1533         pixperstep = pixpex * minstep;
1534         if(pixperstep > 5){minoridx = i;}
1535        if(pixperstep > 2 *  im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1536     }
1537    
1538    x0=im->xorigin;
1539    x1=im->xorigin+im->xsize;
1540     /* paint minor grid */
1541     for (value = pow((double)10, log10(im->minval) 
1542                           - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1543          value  <= im->maxval;
1544          value *= yloglab[minoridx][0]){
1545         if (value < im->minval) continue;
1546         i=0;    
1547         while(yloglab[minoridx][++i] > 0){          
1548            y0 = ytr(im,value * yloglab[minoridx][i]);
1549            if (y0 <= im->yorigin - im->ysize) break;
1550            gfx_new_line ( canvas,
1551                           x0-1,y0,
1552                           x1+1,y0,
1553                           GRIDWIDTH, im->graph_col[GRC_GRID] );
1554         }
1555     }
1556
1557     /* paint major grid and labels*/
1558     for (value = pow((double)10, log10(im->minval) 
1559                           - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1560          value <= im->maxval;
1561          value *= yloglab[majoridx][0]){
1562         if (value < im->minval) continue;
1563         i=0;    
1564         while(yloglab[majoridx][++i] > 0){          
1565            y0 = ytr(im,value * yloglab[majoridx][i]);    
1566            if (y0 <= im->yorigin - im->ysize) break;
1567            gfx_new_line ( canvas,
1568                           x0-2,y0,
1569                           x1+2,y0,
1570                           MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1571            
1572            sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1573            gfx_new_text ( canvas,
1574                           x0-im->text_prop[TEXT_PROP_AXIS].size/1.5, y0,
1575                           im->graph_col[GRC_FONT],
1576                           im->text_prop[TEXT_PROP_AXIS].font,
1577                           im->text_prop[TEXT_PROP_AXIS].size,
1578                           im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1579                           graph_label );
1580         } 
1581     }
1582         return 1;
1583 }
1584
1585
1586 void
1587 vertical_grid(
1588     gfx_canvas_t   *canvas,
1589     image_desc_t   *im )
1590 {   
1591     int xlab_sel;               /* which sort of label and grid ? */
1592     time_t ti, tilab;
1593     long factor;
1594     char graph_label[100];
1595     double x0,y0,y1; /* points for filled graph and more*/
1596    
1597
1598     /* the type of time grid is determined by finding
1599        the number of seconds per pixel in the graph */
1600     
1601     
1602     if(im->xlab_user.minsec == -1){
1603         factor=(im->end - im->start)/im->xsize;
1604         xlab_sel=0;
1605         while ( xlab[xlab_sel+1].minsec != -1 
1606                 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1607         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1608         im->xlab_user.gridst = xlab[xlab_sel].gridst;
1609         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1610         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1611         im->xlab_user.labtm = xlab[xlab_sel].labtm;
1612         im->xlab_user.labst = xlab[xlab_sel].labst;
1613         im->xlab_user.precis = xlab[xlab_sel].precis;
1614         im->xlab_user.stst = xlab[xlab_sel].stst;
1615     }
1616     
1617     /* y coords are the same for every line ... */
1618     y0 = im->yorigin;
1619     y1 = im->yorigin-im->ysize;
1620    
1621
1622     /* paint the minor grid */
1623     for(ti = find_first_time(im->start,
1624                             im->xlab_user.gridtm,
1625                             im->xlab_user.gridst);
1626         ti < im->end; 
1627         ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1628         ){
1629         /* are we inside the graph ? */
1630         if (ti < im->start || ti > im->end) continue;
1631        x0 = xtr(im,ti);       
1632        gfx_new_line(canvas,x0,y0+1, x0,y1-1,GRIDWIDTH, im->graph_col[GRC_GRID]);
1633        
1634     }
1635
1636     /* paint the major grid */
1637     for(ti = find_first_time(im->start,
1638                             im->xlab_user.mgridtm,
1639                             im->xlab_user.mgridst);
1640         ti < im->end; 
1641         ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1642         ){
1643         /* are we inside the graph ? */
1644         if (ti < im->start || ti > im->end) continue;
1645        x0 = xtr(im,ti);
1646        gfx_new_line(canvas,x0,y0+2, x0,y1-2,MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1647        
1648     }
1649     /* paint the labels below the graph */
1650     for(ti = find_first_time(im->start,
1651                             im->xlab_user.labtm,
1652                             im->xlab_user.labst);
1653         ti <= im->end; 
1654         ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1655         ){
1656         tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1657         /* are we inside the graph ? */
1658         if (ti < im->start || ti > im->end) continue;
1659
1660 #if HAVE_STRFTIME
1661         strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1662 #else
1663 # error "your libc has no strftime I guess we'll abort the exercise here."
1664 #endif
1665        gfx_new_text ( canvas,
1666                       xtr(im,tilab), y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1667                       im->graph_col[GRC_FONT],
1668                       im->text_prop[TEXT_PROP_AXIS].font,
1669                       im->text_prop[TEXT_PROP_AXIS].size,
1670                       im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1671                       graph_label );
1672        
1673     }
1674
1675 }
1676
1677
1678 void 
1679 axis_paint(
1680    image_desc_t   *im,
1681    gfx_canvas_t   *canvas
1682            )
1683 {   
1684     /* draw x and y axis */
1685     gfx_new_line ( canvas, im->xorigin+im->xsize,im->yorigin,
1686                       im->xorigin+im->xsize,im->yorigin-im->ysize,
1687                       GRIDWIDTH, im->graph_col[GRC_GRID]);
1688        
1689        gfx_new_line ( canvas, im->xorigin,im->yorigin-im->ysize,
1690                          im->xorigin+im->xsize,im->yorigin-im->ysize,
1691                          GRIDWIDTH, im->graph_col[GRC_GRID]);
1692    
1693        gfx_new_line ( canvas, im->xorigin-4,im->yorigin,
1694                          im->xorigin+im->xsize+4,im->yorigin,
1695                          MGRIDWIDTH, im->graph_col[GRC_GRID]);
1696    
1697        gfx_new_line ( canvas, im->xorigin,im->yorigin+4,
1698                          im->xorigin,im->yorigin-im->ysize-4,
1699                          MGRIDWIDTH, im->graph_col[GRC_GRID]);
1700    
1701     
1702     /* arrow for X axis direction */
1703     gfx_new_area ( canvas, 
1704                    im->xorigin+im->xsize+4,  im->yorigin-3,
1705                    im->xorigin+im->xsize+4,  im->yorigin+3,
1706                    im->xorigin+im->xsize+9,  im->yorigin,  
1707                    im->graph_col[GRC_ARROW]);
1708    
1709    
1710    
1711 }
1712
1713 void
1714 grid_paint(
1715     image_desc_t   *im,
1716     gfx_canvas_t   *canvas
1717            
1718     )
1719 {   
1720     long i;
1721     int res=0;
1722     double x0,x1,x2,x3,y0,y1,y2,y3; /* points for filled graph and more*/
1723     gfx_node_t *node;
1724
1725     /* draw 3d border */
1726     node = gfx_new_area (canvas, 0,im->ygif,
1727                                  2,im->ygif-2,
1728                                  2,2,im->graph_col[GRC_SHADEA]);
1729     gfx_add_point( node , im->xgif - 2, 2 );
1730     gfx_add_point( node , im->xgif, 0 );
1731     gfx_add_point( node , 0,0 );
1732 /*    gfx_add_point( node , 0,im->ygif ); */
1733    
1734     node =  gfx_new_area (canvas, 2,im->ygif-2,
1735                                   im->xgif-2,im->ygif-2,
1736                                   im->xgif - 2, 2,
1737                                  im->graph_col[GRC_SHADEB]);
1738     gfx_add_point( node ,   im->xgif,0);
1739     gfx_add_point( node ,   im->xgif,im->ygif);
1740     gfx_add_point( node ,   0,im->ygif);
1741 /*    gfx_add_point( node , 0,im->ygif ); */
1742    
1743    
1744     if (im->draw_x_grid == 1 )
1745       vertical_grid(canvas, im);
1746     
1747     if (im->draw_y_grid == 1){
1748         if(im->logarithmic){
1749                 res = horizontal_log_grid(canvas,im);
1750         } else {
1751                 res = horizontal_grid(canvas,im);
1752         }
1753
1754         /* dont draw horizontal grid if there is no min and max val */
1755         if (! res ) {
1756           char *nodata = "No Data found";
1757            gfx_new_text(canvas,im->xgif/2, (2*im->yorigin-im->ysize) / 2,
1758                         im->graph_col[GRC_FONT],
1759                         im->text_prop[TEXT_PROP_AXIS].font,
1760                         im->text_prop[TEXT_PROP_AXIS].size,
1761                         im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1762                         nodata );          
1763         }
1764     }
1765
1766     /* yaxis description */
1767         #if 0
1768             gfx_new_text( canvas,
1769                           7, (im->yorigin - im->ysize/2),
1770                           im->graph_col[GRC_FONT],
1771                           im->text_prop[TEXT_PROP_AXIS].font,
1772                           im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1773                           GFX_H_CENTER, GFX_V_CENTER,
1774                           im->ylegend);
1775         #else
1776             /* horrible hack until we can actually print vertically */
1777             {
1778                 int n;
1779                 int l=strlen(im->ylegend);
1780                 char s[2];
1781                 for (n=0;n<strlen(im->ylegend);n++) {
1782                     s[0]=im->ylegend[n];
1783                     s[1]='\0';
1784                     gfx_new_text(canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1785                         im->graph_col[GRC_FONT],
1786                         im->text_prop[TEXT_PROP_AXIS].font,
1787                         im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1788                         GFX_H_CENTER, GFX_V_CENTER,
1789                         s);
1790                 }
1791             }
1792         #endif
1793    
1794     /* graph title */
1795     gfx_new_text( canvas,
1796                   im->xgif/2, im->text_prop[TEXT_PROP_TITLE].size,
1797                   im->graph_col[GRC_FONT],
1798                   im->text_prop[TEXT_PROP_TITLE].font,
1799                   im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1800                   GFX_H_CENTER, GFX_V_CENTER,
1801                   im->title);
1802
1803     /* graph labels */
1804     if( !(im->extra_flags & NOLEGEND) ) {
1805       for(i=0;i<im->gdes_c;i++){
1806         if(im->gdes[i].legend[0] =='\0')
1807             continue;
1808          
1809         /* im->gdes[i].leg_y is the bottom of the legend */
1810                 x0 = im->gdes[i].leg_x;
1811                 y0 = im->gdes[i].leg_y;
1812                 /* Box needed? */
1813                 if (       im->gdes[i].gf != GF_GPRINT
1814                         && im->gdes[i].gf != GF_COMMENT) {
1815                     int boxH, boxV;
1816
1817                     boxH = gfx_get_text_width(0,
1818                                 im->text_prop[TEXT_PROP_AXIS].font,
1819                                 im->text_prop[TEXT_PROP_AXIS].size,
1820                                 im->tabwidth,"M") * 1.25;
1821                     boxV = boxH;
1822
1823                     node = gfx_new_area(canvas,
1824                                 x0,y0-boxV,
1825                                 x0,y0,
1826                                 x0+boxH,y0,
1827                                 im->gdes[i].col);
1828                     gfx_add_point ( node, x0+boxH, y0-boxV );
1829                     node = gfx_new_line(canvas,
1830                                 x0,y0-boxV, x0,y0,
1831                                 1,0x000000FF);
1832                     gfx_add_point(node,x0+boxH,y0);
1833                     gfx_add_point(node,x0+boxH,y0-boxV);
1834                     gfx_add_point(node,x0,y0-boxV);
1835                     x0 += boxH / 1.25 * 2;
1836                 }
1837                 gfx_new_text ( canvas, x0, y0,
1838                                    im->graph_col[GRC_FONT],
1839                                    im->text_prop[TEXT_PROP_AXIS].font,
1840                                    im->text_prop[TEXT_PROP_AXIS].size,
1841                                    im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1842                                    im->gdes[i].legend );
1843               }
1844            }
1845         }
1846
1847
1848 /*****************************************************
1849  * lazy check make sure we rely need to create this graph
1850  *****************************************************/
1851
1852 int lazy_check(image_desc_t *im){
1853     FILE *fd = NULL;
1854         int size = 1;
1855     struct stat  gifstat;
1856     
1857     if (im->lazy == 0) return 0; /* no lazy option */
1858     if (stat(im->graphfile,&gifstat) != 0) 
1859       return 0; /* can't stat */
1860     /* one pixel in the existing graph is more then what we would
1861        change here ... */
1862     if (time(NULL) - gifstat.st_mtime > 
1863         (im->end - im->start) / im->xsize) 
1864       return 0;
1865     if ((fd = fopen(im->graphfile,"rb")) == NULL) 
1866       return 0; /* the file does not exist */
1867     switch (im->imgformat) {
1868     case IF_PNG:
1869            size = PngSize(fd,&(im->xgif),&(im->ygif));
1870            break;
1871     }
1872     fclose(fd);
1873     return size;
1874 }
1875
1876 void
1877 pie_part(gfx_canvas_t *canvas, gfx_color_t color,
1878             double PieCenterX, double PieCenterY, double Radius,
1879             double startangle, double endangle)
1880 {
1881     gfx_node_t *node;
1882     double angle;
1883     double step=M_PI/50; /* Number of iterations for the circle;
1884                          ** 10 is definitely too low, more than
1885                          ** 50 seems to be overkill
1886                          */
1887
1888     /* Strange but true: we have to work clockwise or else
1889     ** anti aliasing nor transparency don't work.
1890     **
1891     ** This test is here to make sure we do it right, also
1892     ** this makes the for...next loop more easy to implement.
1893     ** The return will occur if the user enters a negative number
1894     ** (which shouldn't be done according to the specs) or if the
1895     ** programmers do something wrong (which, as we all know, never
1896     ** happens anyway :)
1897     */
1898     if (endangle<startangle) return;
1899
1900     /* Hidden feature: Radius decreases each full circle */
1901     angle=startangle;
1902     while (angle>=2*M_PI) {
1903         angle  -= 2*M_PI;
1904         Radius *= 0.8;
1905     }
1906
1907     node=gfx_new_area(canvas,
1908                 PieCenterX+sin(startangle)*Radius,
1909                 PieCenterY-cos(startangle)*Radius,
1910                 PieCenterX,
1911                 PieCenterY,
1912                 PieCenterX+sin(endangle)*Radius,
1913                 PieCenterY-cos(endangle)*Radius,
1914                 color);
1915     for (angle=endangle;angle-startangle>=step;angle-=step) {
1916         gfx_add_point(node,
1917                 PieCenterX+sin(angle)*Radius,
1918                 PieCenterY-cos(angle)*Radius );
1919     }
1920 }
1921
1922 int
1923 graph_size_location(image_desc_t *im, int elements, int piechart )
1924 {
1925     /* The actual size of the image to draw is determined from
1926     ** several sources.  The size given on the command line is
1927     ** the graph area but we need more as we have to draw labels
1928     ** and other things outside the graph area
1929     */
1930
1931     /* +-+-------------------------------------------+
1932     ** |l|.................title.....................|
1933     ** |e+--+-------------------------------+--------+
1934     ** |b| b|                               |        |
1935     ** |a| a|                               |  pie   |
1936     ** |l| l|          main graph area      | chart  |
1937     ** |.| .|                               |  area  |
1938     ** |t| y|                               |        |
1939     ** |r+--+-------------------------------+--------+
1940     ** |e|  | x-axis labels                 |        |
1941     ** |v+--+-------------------------------+--------+
1942     ** | |..............legends......................|
1943     ** +-+-------------------------------------------+
1944     */
1945     int Xvertical=0,    Yvertical=0,
1946         Xtitle   =0,    Ytitle   =0,
1947         Xylabel  =0,    Yylabel  =0,
1948         Xmain    =0,    Ymain    =0,
1949         Xpie     =0,    Ypie     =0,
1950         Xxlabel  =0,    Yxlabel  =0,
1951         Xlegend  =0,    Ylegend  =0,
1952         Xspacing =10,   Yspacing =10;
1953
1954     if (im->ylegend[0] != '\0') {
1955         Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
1956         Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
1957     }
1958
1959     if (im->title[0] != '\0') {
1960         /* The title is placed "inbetween" two text lines so it
1961         ** automatically has some vertical spacing.  The horizontal
1962         ** spacing is added here, on each side.
1963         */
1964         Xtitle = gfx_get_text_width(0,
1965                 im->text_prop[TEXT_PROP_TITLE].font,
1966                 im->text_prop[TEXT_PROP_TITLE].size,
1967                 im->tabwidth,
1968                 im->title) + 2*Xspacing;
1969         Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
1970     }
1971
1972     if (elements) {
1973         Xmain=im->xsize;
1974         Ymain=im->ysize;
1975         if (im->draw_x_grid) {
1976             Xxlabel=Xmain;
1977             Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
1978         }
1979         if (im->draw_y_grid) {
1980             Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
1981             Yylabel=Ymain;
1982         }
1983     }
1984
1985     if (piechart) {
1986         im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
1987         Xpie=im->piesize;
1988         Ypie=im->piesize;
1989     }
1990
1991     /* Now calculate the total size.  Insert some spacing where
1992        desired.  im->xorigin and im->yorigin need to correspond
1993        with the lower left corner of the main graph area or, if
1994        this one is not set, the imaginary box surrounding the
1995        pie chart area. */
1996
1997     /* The legend width cannot yet be determined, as a result we
1998     ** have problems adjusting the image to it.  For now, we just
1999     ** forget about it at all; the legend will have to fit in the
2000     ** size already allocated.
2001     */
2002     im->xgif = Xylabel + Xmain + Xpie + Xspacing;
2003     if (Xmain) im->xgif += Xspacing;
2004     if (Xpie) im->xgif += Xspacing;
2005     im->xorigin = Xspacing + Xylabel;
2006     if (Xtitle > im->xgif) im->xgif = Xtitle;
2007     if (Xvertical) {
2008         im->xgif += Xvertical;
2009         im->xorigin += Xvertical;
2010     }
2011     xtr(im,0);
2012
2013     /* The vertical size is interesting... we need to compare
2014     ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2015     ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2016     ** start even thinking about Ylegend.
2017     **
2018     ** Do it in three portions: First calculate the inner part,
2019     ** then do the legend, then adjust the total height of the img.
2020     */
2021
2022     /* reserve space for main and/or pie */
2023     im->ygif = Ymain + Yxlabel;
2024     if (im->ygif < Ypie) im->ygif = Ypie;
2025     im->yorigin = im->ygif - Yxlabel;
2026     /* reserve space for the title *or* some padding above the graph */
2027     if (Ytitle) {
2028         im->ygif += Ytitle;
2029         im->yorigin += Ytitle;
2030     } else {
2031         im->ygif += Yspacing;
2032         im->yorigin += Yspacing;
2033     }
2034     /* reserve space for padding below the graph */
2035     im->ygif += Yspacing;
2036     ytr(im,DNAN);
2037
2038     /* Determine where to place the legends onto the image.
2039     ** Adjust im->ygif to match the space requirements.
2040     */
2041     if(leg_place(im)==-1)
2042         return -1;
2043
2044     /* last of three steps: check total height of image */
2045     if (im->ygif < Yvertical) im->ygif = Yvertical;
2046
2047 #if 0
2048     if (Xlegend > im->xgif) {
2049         im->xgif = Xlegend;
2050         /* reposition Pie */
2051 #endif
2052
2053     /* The pie is placed in the upper right hand corner,
2054     ** just below the title (if any) and with sufficient
2055     ** padding.
2056     */
2057     im->pie_x = im->xgif - Xspacing - Xpie/2;
2058     im->pie_y = im->yorigin-Ymain+Ypie/2;
2059
2060     return 0;
2061 }
2062
2063 /* draw that picture thing ... */
2064 int
2065 graph_paint(image_desc_t *im, char ***calcpr)
2066 {
2067   int i,ii;
2068   int lazy =     lazy_check(im);
2069   int piechart = 0;
2070   double PieStart=0.0;
2071   FILE  *fo;
2072   gfx_canvas_t *canvas;
2073   gfx_node_t *node;
2074   
2075   double areazero = 0.0;
2076   enum gf_en stack_gf = GF_PRINT;
2077   graph_desc_t *lastgdes = NULL;    
2078   
2079   /* if we are lazy and there is nothing to PRINT ... quit now */
2080   if (lazy && im->prt_c==0) return 0;
2081   
2082   /* pull the data from the rrd files ... */
2083   
2084   if(data_fetch(im)==-1)
2085     return -1;
2086   
2087   /* evaluate VDEF and CDEF operations ... */
2088   if(data_calc(im)==-1)
2089     return -1;
2090   
2091   /* check if we need to draw a piechart */
2092   for(i=0;i<im->gdes_c;i++){
2093     if (im->gdes[i].gf == GF_PART) {
2094       piechart=1;
2095       break;
2096     }
2097   }
2098
2099   /* calculate and PRINT and GPRINT definitions. We have to do it at
2100    * this point because it will affect the length of the legends
2101    * if there are no graph elements we stop here ... 
2102    * if we are lazy, try to quit ... 
2103    */
2104   i=print_calc(im,calcpr);
2105   if(i<0) return -1;
2106   if(((i==0)&&(piechart==0)) || lazy) return 0;
2107
2108   /* If there's only the pie chart to draw, signal this */
2109   if (i==0) piechart=2;
2110   
2111   /* get actual drawing data and find min and max values*/
2112   if(data_proc(im)==-1)
2113     return -1;
2114   
2115   if(!im->logarithmic){si_unit(im);}        /* identify si magnitude Kilo, Mega Giga ? */
2116   
2117   if(!im->rigid && ! im->logarithmic)
2118     expand_range(im);   /* make sure the upper and lower limit are
2119                            sensible values */
2120
2121 /**************************************************************
2122  *** Calculating sizes and locations became a bit confusing ***
2123  *** so I moved this into a separate function.              ***
2124  **************************************************************/
2125   if(graph_size_location(im,i,piechart)==-1)
2126     return -1;
2127
2128   canvas=gfx_new_canvas();
2129
2130   /* the actual graph is created by going through the individual
2131      graph elements and then drawing them */
2132   
2133   node=gfx_new_area ( canvas,
2134                       0, 0,
2135                       im->xgif, 0,
2136                       im->xgif, im->ygif,
2137                       im->graph_col[GRC_BACK]);
2138
2139   gfx_add_point(node,0, im->ygif);
2140
2141   if (piechart != 2) {
2142     node=gfx_new_area ( canvas,
2143                       im->xorigin,             im->yorigin, 
2144                       im->xorigin + im->xsize, im->yorigin,
2145                       im->xorigin + im->xsize, im->yorigin-im->ysize,
2146                       im->graph_col[GRC_CANVAS]);
2147   
2148     gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2149
2150     if (im->minval > 0.0)
2151       areazero = im->minval;
2152     if (im->maxval < 0.0)
2153       areazero = im->maxval;
2154   
2155     axis_paint(im,canvas);
2156   }
2157
2158   if (piechart) {
2159     pie_part(canvas,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2160   }
2161
2162   for(i=0;i<im->gdes_c;i++){
2163     switch(im->gdes[i].gf){
2164     case GF_CDEF:
2165     case GF_VDEF:
2166     case GF_DEF:
2167     case GF_PRINT:
2168     case GF_GPRINT:
2169     case GF_COMMENT:
2170     case GF_HRULE:
2171     case GF_VRULE:
2172       break;
2173     case GF_TICK:
2174       for (ii = 0; ii < im->xsize; ii++)
2175         {
2176           if (!isnan(im->gdes[i].p_data[ii]) && 
2177               im->gdes[i].p_data[ii] > 0.0)
2178             { 
2179               /* generate a tick */
2180               gfx_new_line(canvas, im -> xorigin + ii, 
2181                            im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2182                            im -> xorigin + ii, 
2183                            im -> yorigin,
2184                            1.0,
2185                            im -> gdes[i].col );
2186             }
2187         }
2188       break;
2189     case GF_LINE:
2190     case GF_AREA:
2191       stack_gf = im->gdes[i].gf;
2192     case GF_STACK:          
2193       /* fix data points at oo and -oo */
2194       for(ii=0;ii<im->xsize;ii++){
2195         if (isinf(im->gdes[i].p_data[ii])){
2196           if (im->gdes[i].p_data[ii] > 0) {
2197             im->gdes[i].p_data[ii] = im->maxval ;
2198           } else {
2199             im->gdes[i].p_data[ii] = im->minval ;
2200           }                 
2201           
2202         }
2203       } /* for */
2204       
2205       if (im->gdes[i].col != 0x0){               
2206         /* GF_LINE and friend */
2207         if(stack_gf == GF_LINE ){
2208           node = NULL;
2209           for(ii=1;ii<im->xsize;ii++){
2210             if ( ! isnan(im->gdes[i].p_data[ii-1])
2211                  && ! isnan(im->gdes[i].p_data[ii])){
2212               if (node == NULL){
2213                 node = gfx_new_line(canvas,
2214                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2215                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2216                                     im->gdes[i].linewidth,
2217                                     im->gdes[i].col);
2218               } else {
2219                 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2220               }
2221             } else {
2222               node = NULL;
2223             }
2224           }
2225         } else {
2226           int area_start=-1;
2227           node = NULL;
2228           for(ii=1;ii<im->xsize;ii++){
2229             /* open an area */
2230             if ( ! isnan(im->gdes[i].p_data[ii-1])
2231                  && ! isnan(im->gdes[i].p_data[ii])){
2232               if (node == NULL){
2233                 float ybase = 0.0;
2234                 if (im->gdes[i].gf == GF_STACK) {
2235                   ybase = ytr(im,lastgdes->p_data[ii-1]);
2236                 } else {
2237                   ybase =  ytr(im,areazero);
2238                 }
2239                 area_start = ii-1;
2240                 node = gfx_new_area(canvas,
2241                                     ii-1+im->xorigin,ybase,
2242                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2243                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2244                                     im->gdes[i].col
2245                                     );
2246               } else {
2247                 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2248               }
2249             }
2250
2251             if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2252               /* GF_AREA STACK type*/
2253               if (im->gdes[i].gf == GF_STACK ) {
2254                 int iii;
2255                 for (iii=ii-1;iii>area_start;iii--){
2256                   gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2257                 }
2258               } else {
2259                 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2260               };
2261               node=NULL;
2262             };
2263           }             
2264         } /* else GF_LINE */
2265       } /* if color != 0x0 */
2266       /* make sure we do not run into trouble when stacking on NaN */
2267       for(ii=0;ii<im->xsize;ii++){
2268         if (isnan(im->gdes[i].p_data[ii])) {
2269           double ybase = 0.0;
2270           if (lastgdes) {
2271             ybase = ytr(im,lastgdes->p_data[ii-1]);
2272           };
2273           if (isnan(ybase) || !lastgdes ){
2274             ybase =  ytr(im,areazero);
2275           }
2276           im->gdes[i].p_data[ii] = ybase;
2277         }
2278       } 
2279       lastgdes = &(im->gdes[i]);                         
2280       break;
2281     case GF_PART:
2282       if(isnan(im->gdes[i].yrule)) /* fetch variable */
2283         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2284      
2285       if (finite(im->gdes[i].yrule)) {  /* even the fetched var can be NaN */
2286         pie_part(canvas,im->gdes[i].col,
2287                 im->pie_x,im->pie_y,im->piesize*0.4,
2288                 M_PI*2.0*PieStart/100.0,
2289                 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2290         PieStart += im->gdes[i].yrule;
2291       }
2292       break;
2293     } /* switch */
2294   }
2295   if (piechart==2) {
2296     im->draw_x_grid=0;
2297     im->draw_y_grid=0;
2298   }
2299   /* grid_paint also does the text */
2300   grid_paint(im,canvas);
2301   
2302   /* the RULES are the last thing to paint ... */
2303   for(i=0;i<im->gdes_c;i++){    
2304     
2305     switch(im->gdes[i].gf){
2306     case GF_HRULE:
2307       if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2308         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2309       };
2310       if(im->gdes[i].yrule >= im->minval
2311          && im->gdes[i].yrule <= im->maxval)
2312         gfx_new_line(canvas,
2313                      im->xorigin,ytr(im,im->gdes[i].yrule),
2314                      im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2315                      1.0,im->gdes[i].col); 
2316       break;
2317     case GF_VRULE:
2318       if(im->gdes[i].xrule == 0) { /* fetch variable */
2319         im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2320       };
2321       if(im->gdes[i].xrule >= im->start
2322          && im->gdes[i].xrule <= im->end)
2323         gfx_new_line(canvas,
2324                      xtr(im,im->gdes[i].xrule),im->yorigin,
2325                      xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2326                      1.0,im->gdes[i].col); 
2327       break;
2328     default:
2329       break;
2330     }
2331   }
2332
2333   
2334   if (strcmp(im->graphfile,"-")==0) {
2335 #ifdef WIN32
2336     /* Change translation mode for stdout to BINARY */
2337     _setmode( _fileno( stdout ), O_BINARY );
2338 #endif
2339     fo = stdout;
2340   } else {
2341     if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2342       rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2343                     strerror(errno));
2344       return (-1);
2345     }
2346   }
2347   switch (im->imgformat) {
2348   case IF_PNG:
2349     gfx_render_png (canvas,im->xgif,im->ygif,im->zoom,0x0,fo);
2350     break;
2351   }
2352   if (strcmp(im->graphfile,"-") != 0)
2353     fclose(fo);
2354    
2355   gfx_destroy(canvas);
2356   return 0;
2357 }
2358
2359
2360 /*****************************************************
2361  * graph stuff 
2362  *****************************************************/
2363
2364 int
2365 gdes_alloc(image_desc_t *im){
2366
2367     long def_step = (im->end-im->start)/im->xsize;
2368     
2369     if (im->step > def_step) /* step can be increassed ... no decreassed */
2370       def_step = im->step;
2371
2372     im->gdes_c++;
2373     
2374     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2375                                            * sizeof(graph_desc_t)))==NULL){
2376         rrd_set_error("realloc graph_descs");
2377         return -1;
2378     }
2379
2380
2381     im->gdes[im->gdes_c-1].step=def_step; 
2382     im->gdes[im->gdes_c-1].start=im->start; 
2383     im->gdes[im->gdes_c-1].end=im->end; 
2384     im->gdes[im->gdes_c-1].vname[0]='\0'; 
2385     im->gdes[im->gdes_c-1].data=NULL;
2386     im->gdes[im->gdes_c-1].ds_namv=NULL;
2387     im->gdes[im->gdes_c-1].data_first=0;
2388     im->gdes[im->gdes_c-1].p_data=NULL;
2389     im->gdes[im->gdes_c-1].rpnp=NULL;
2390     im->gdes[im->gdes_c-1].col = 0x0;
2391     im->gdes[im->gdes_c-1].legend[0]='\0';
2392     im->gdes[im->gdes_c-1].rrd[0]='\0';
2393     im->gdes[im->gdes_c-1].ds=-1;    
2394     im->gdes[im->gdes_c-1].p_data=NULL;    
2395     return 0;
2396 }
2397
2398 /* copies input untill the first unescaped colon is found
2399    or until input ends. backslashes have to be escaped as well */
2400 int
2401 scan_for_col(char *input, int len, char *output)
2402 {
2403     int inp,outp=0;
2404     for (inp=0; 
2405          inp < len &&
2406            input[inp] != ':' &&
2407            input[inp] != '\0';
2408          inp++){
2409       if (input[inp] == '\\' &&
2410           input[inp+1] != '\0' && 
2411           (input[inp+1] == '\\' ||
2412            input[inp+1] == ':')){
2413         output[outp++] = input[++inp];
2414       }
2415       else {
2416         output[outp++] = input[inp];
2417       }
2418     }
2419     output[outp] = '\0';
2420     return inp;
2421 }
2422
2423 /* Some surgery done on this function, it became ridiculously big.
2424 ** Things moved:
2425 ** - initializing     now in rrd_graph_init()
2426 ** - options parsing  now in rrd_graph_options()
2427 ** - script parsing   now in rrd_graph_script()
2428 */
2429 int 
2430 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2431 {
2432     image_desc_t   im;
2433
2434     rrd_graph_init(&im);
2435
2436     rrd_graph_options(argc,argv,&im);
2437     if (rrd_test_error()) return -1;
2438     
2439     if (strlen(argv[optind])>=MAXPATH) {
2440         rrd_set_error("filename (including path) too long");
2441         return -1;
2442     }
2443     strncpy(im.graphfile,argv[optind],MAXPATH-1);
2444     im.graphfile[MAXPATH-1]='\0';
2445
2446     rrd_graph_script(argc,argv,&im);
2447     if (rrd_test_error()) return -1;
2448
2449     /* Everything is now read and the actual work can start */
2450
2451     (*prdata)=NULL;
2452     if (graph_paint(&im,prdata)==-1){
2453         im_free(&im);
2454         return -1;
2455     }
2456
2457     /* The image is generated and needs to be output.
2458     ** Also, if needed, print a line with information about the image.
2459     */
2460
2461     *xsize=im.xgif;
2462     *ysize=im.ygif;
2463     if (im.imginfo) {
2464         char *filename;
2465         if (!(*prdata)) {
2466             /* maybe prdata is not allocated yet ... lets do it now */
2467             if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2468                 rrd_set_error("malloc imginfo");
2469                 return -1; 
2470             };
2471         }
2472         if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2473          ==NULL){
2474             rrd_set_error("malloc imginfo");
2475             return -1;
2476         }
2477         filename=im.graphfile+strlen(im.graphfile);
2478         while(filename > im.graphfile) {
2479             if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2480             filename--;
2481         }
2482
2483         sprintf((*prdata)[0],im.imginfo,filename,(long)(im.zoom*im.xgif),(long)(im.zoom*im.ygif));
2484     }
2485     im_free(&im);
2486     return 0;
2487 }
2488
2489 void
2490 rrd_graph_init(image_desc_t *im)
2491 {
2492     int i;
2493
2494     im->xlab_user.minsec = -1;
2495     im->xgif=0;
2496     im->ygif=0;
2497     im->xsize = 400;
2498     im->ysize = 100;
2499     im->step = 0;
2500     im->ylegend[0] = '\0';
2501     im->title[0] = '\0';
2502     im->minval = DNAN;
2503     im->maxval = DNAN;    
2504     im->interlaced = 0;
2505     im->unitsexponent= 9999;
2506     im->extra_flags= 0;
2507     im->rigid = 0;
2508     im->imginfo = NULL;
2509     im->lazy = 0;
2510     im->logarithmic = 0;
2511     im->ygridstep = DNAN;
2512     im->draw_x_grid = 1;
2513     im->draw_y_grid = 1;
2514     im->base = 1000;
2515     im->prt_c = 0;
2516     im->gdes_c = 0;
2517     im->gdes = NULL;
2518     im->zoom = 1.0;
2519     im->imgformat = IF_PNG; /* we default to PNG output */
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->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->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]#%8x",
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 -1;
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->zoom= atof(optarg);
2787             if (im->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,"#%6x%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,"#%8x%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 }