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