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