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