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