big spell checking patch -- slif@bellsouth.net
[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 really 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) & !(im->extra_flags & ONLY_GRAPH) ) {
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, 
1904                           RRDGRAPH_YLEGEND_ANGLE,
1905                           GFX_H_LEFT, GFX_V_CENTER,
1906                           im->ylegend);
1907         } else {
1908             /* horrible hack until we can actually print vertically */
1909             {
1910                 int n;
1911                 int l=strlen(im->ylegend);
1912                 char s[2];
1913                 for (n=0;n<strlen(im->ylegend);n++) {
1914                     s[0]=im->ylegend[n];
1915                     s[1]='\0';
1916                     gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(n+1),
1917                         im->graph_col[GRC_FONT],
1918                         im->text_prop[TEXT_PROP_AXIS].font,
1919                         im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1920                         GFX_H_CENTER, GFX_V_CENTER,
1921                         s);
1922                 }
1923             }
1924         }
1925    
1926     /* graph title */
1927     gfx_new_text( im->canvas,
1928                   im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1929                   im->graph_col[GRC_FONT],
1930                   im->text_prop[TEXT_PROP_TITLE].font,
1931                   im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1932                   GFX_H_CENTER, GFX_V_CENTER,
1933                   im->title);
1934
1935     /* graph labels */
1936     if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1937       for(i=0;i<im->gdes_c;i++){
1938         if(im->gdes[i].legend[0] =='\0')
1939             continue;
1940          
1941         /* im->gdes[i].leg_y is the bottom of the legend */
1942                 X0 = im->gdes[i].leg_x;
1943                 Y0 = im->gdes[i].leg_y;
1944                 /* Box needed? */
1945                 if (       im->gdes[i].gf != GF_GPRINT
1946                         && im->gdes[i].gf != GF_COMMENT) {
1947                     int boxH, boxV;
1948
1949                     boxH = gfx_get_text_width(im->canvas, 0,
1950                                 im->text_prop[TEXT_PROP_AXIS].font,
1951                                 im->text_prop[TEXT_PROP_AXIS].size,
1952                                 im->tabwidth,"M", 0) * 1.25;
1953                     boxV = boxH;
1954
1955                     node = gfx_new_area(im->canvas,
1956                                 X0,Y0-boxV,
1957                                 X0,Y0,
1958                                 X0+boxH,Y0,
1959                                 im->gdes[i].col);
1960                     gfx_add_point ( node, X0+boxH, Y0-boxV );
1961                     node = gfx_new_line(im->canvas,
1962                                 X0,Y0-boxV, X0,Y0,
1963                                 1,0x000000FF);
1964                     gfx_add_point(node,X0+boxH,Y0);
1965                     gfx_add_point(node,X0+boxH,Y0-boxV);
1966                     gfx_close_path(node);
1967                     X0 += boxH / 1.25 * 2;
1968                 }
1969                 gfx_new_text ( im->canvas, X0, Y0,
1970                                    im->graph_col[GRC_FONT],
1971                                    im->text_prop[TEXT_PROP_AXIS].font,
1972                                    im->text_prop[TEXT_PROP_AXIS].size,
1973                                    im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1974                                    im->gdes[i].legend );
1975               }
1976            }
1977         }
1978
1979
1980 /*****************************************************
1981  * lazy check make sure we rely need to create this graph
1982  *****************************************************/
1983
1984 int lazy_check(image_desc_t *im){
1985     FILE *fd = NULL;
1986         int size = 1;
1987     struct stat  imgstat;
1988     
1989     if (im->lazy == 0) return 0; /* no lazy option */
1990     if (stat(im->graphfile,&imgstat) != 0) 
1991       return 0; /* can't stat */
1992     /* one pixel in the existing graph is more then what we would
1993        change here ... */
1994     if (time(NULL) - imgstat.st_mtime > 
1995         (im->end - im->start) / im->xsize) 
1996       return 0;
1997     if ((fd = fopen(im->graphfile,"rb")) == NULL) 
1998       return 0; /* the file does not exist */
1999     switch (im->canvas->imgformat) {
2000     case IF_PNG:
2001            size = PngSize(fd,&(im->ximg),&(im->yimg));
2002            break;
2003     default:
2004            size = 1;
2005     }
2006     fclose(fd);
2007     return size;
2008 }
2009
2010 void
2011 pie_part(image_desc_t *im, gfx_color_t color,
2012             double PieCenterX, double PieCenterY, double Radius,
2013             double startangle, double endangle)
2014 {
2015     gfx_node_t *node;
2016     double angle;
2017     double step=M_PI/50; /* Number of iterations for the circle;
2018                          ** 10 is definitely too low, more than
2019                          ** 50 seems to be overkill
2020                          */
2021
2022     /* Strange but true: we have to work clockwise or else
2023     ** anti aliasing nor transparency don't work.
2024     **
2025     ** This test is here to make sure we do it right, also
2026     ** this makes the for...next loop more easy to implement.
2027     ** The return will occur if the user enters a negative number
2028     ** (which shouldn't be done according to the specs) or if the
2029     ** programmers do something wrong (which, as we all know, never
2030     ** happens anyway :)
2031     */
2032     if (endangle<startangle) return;
2033
2034     /* Hidden feature: Radius decreases each full circle */
2035     angle=startangle;
2036     while (angle>=2*M_PI) {
2037         angle  -= 2*M_PI;
2038         Radius *= 0.8;
2039     }
2040
2041     node=gfx_new_area(im->canvas,
2042                 PieCenterX+sin(startangle)*Radius,
2043                 PieCenterY-cos(startangle)*Radius,
2044                 PieCenterX,
2045                 PieCenterY,
2046                 PieCenterX+sin(endangle)*Radius,
2047                 PieCenterY-cos(endangle)*Radius,
2048                 color);
2049     for (angle=endangle;angle-startangle>=step;angle-=step) {
2050         gfx_add_point(node,
2051                 PieCenterX+sin(angle)*Radius,
2052                 PieCenterY-cos(angle)*Radius );
2053     }
2054 }
2055
2056 int
2057 graph_size_location(image_desc_t *im, int elements, int piechart )
2058 {
2059     /* The actual size of the image to draw is determined from
2060     ** several sources.  The size given on the command line is
2061     ** the graph area but we need more as we have to draw labels
2062     ** and other things outside the graph area
2063     */
2064
2065     /* +-+-------------------------------------------+
2066     ** |l|.................title.....................|
2067     ** |e+--+-------------------------------+--------+
2068     ** |b| b|                               |        |
2069     ** |a| a|                               |  pie   |
2070     ** |l| l|          main graph area      | chart  |
2071     ** |.| .|                               |  area  |
2072     ** |t| y|                               |        |
2073     ** |r+--+-------------------------------+--------+
2074     ** |e|  | x-axis labels                 |        |
2075     ** |v+--+-------------------------------+--------+
2076     ** | |..............legends......................|
2077     ** +-+-------------------------------------------+
2078     */
2079     int Xvertical=0,    Yvertical=0,
2080         Xtitle   =0,    Ytitle   =0,
2081         Xylabel  =0,    Yylabel  =0,
2082         Xmain    =0,    Ymain    =0,
2083         Xpie     =0,    Ypie     =0,
2084         Xxlabel  =0,    Yxlabel  =0,
2085 #if 0
2086         Xlegend  =0,    Ylegend  =0,
2087 #endif
2088         Xspacing =10,  Yspacing =10;
2089
2090     if (im->extra_flags & ONLY_GRAPH) {
2091         if ( im->ysize > 32 ) {
2092            rrd_set_error("height > 32 is not possible with --only-graph option");
2093            return -1;
2094         }
2095        Xspacing =0;
2096         Yspacing =0;
2097     } else {
2098         if (im->ylegend[0] != '\0') {
2099            Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2100            Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2101         }
2102     }
2103
2104     if (im->title[0] != '\0') {
2105         /* The title is placed "inbetween" two text lines so it
2106         ** automatically has some vertical spacing.  The horizontal
2107         ** spacing is added here, on each side.
2108         */
2109         Xtitle = gfx_get_text_width(im->canvas, 0,
2110                 im->text_prop[TEXT_PROP_TITLE].font,
2111                 im->text_prop[TEXT_PROP_TITLE].size,
2112                 im->tabwidth,
2113                 im->title, 0) + 2*Xspacing;
2114         Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2115     }
2116
2117     if (elements) {
2118         Xmain=im->xsize;
2119         Ymain=im->ysize;
2120         if (im->draw_x_grid) {
2121             Xxlabel=Xmain;
2122             Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2123         }
2124         if (im->draw_y_grid) {
2125             Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2126             Yylabel=Ymain;
2127         }
2128     }
2129
2130     if (piechart) {
2131         im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2132         Xpie=im->piesize;
2133         Ypie=im->piesize;
2134     }
2135
2136     /* Now calculate the total size.  Insert some spacing where
2137        desired.  im->xorigin and im->yorigin need to correspond
2138        with the lower left corner of the main graph area or, if
2139        this one is not set, the imaginary box surrounding the
2140        pie chart area. */
2141
2142     /* The legend width cannot yet be determined, as a result we
2143     ** have problems adjusting the image to it.  For now, we just
2144     ** forget about it at all; the legend will have to fit in the
2145     ** size already allocated.
2146     */
2147     im->ximg = Xmain;
2148
2149     if ( !(im->extra_flags & ONLY_GRAPH) ) {
2150         im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2151     }
2152
2153     if (Xmain) im->ximg += Xspacing;
2154     if (Xpie) im->ximg += Xspacing;
2155
2156     if (im->extra_flags & ONLY_GRAPH) {
2157        im->xorigin = 0;
2158     } else {
2159        im->xorigin = Xspacing + Xylabel;
2160     }
2161
2162     if (Xtitle > im->ximg) im->ximg = Xtitle;
2163     if (Xvertical) {
2164         im->ximg += Xvertical;
2165         im->xorigin += Xvertical;
2166     }
2167     xtr(im,0);
2168
2169     /* The vertical size is interesting... we need to compare
2170     ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2171     ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2172     ** start even thinking about Ylegend.
2173     **
2174     ** Do it in three portions: First calculate the inner part,
2175     ** then do the legend, then adjust the total height of the img.
2176     */
2177
2178     /* reserve space for main and/or pie */
2179
2180     if (im->extra_flags & ONLY_GRAPH) {
2181         im->yimg = Ymain;
2182     } else {
2183         im->yimg = Ymain + Yxlabel;
2184     }
2185
2186     if (im->yimg < Ypie) im->yimg = Ypie;
2187
2188     if (im->extra_flags & ONLY_GRAPH) {
2189         im->yorigin = im->yimg;
2190     } else {
2191         im->yorigin = im->yimg - Yxlabel;
2192     }
2193
2194     /* reserve space for the title *or* some padding above the graph */
2195     if (Ytitle) {
2196         im->yimg += Ytitle;
2197         im->yorigin += Ytitle;
2198     } else {
2199         im->yimg += Yspacing;
2200         im->yorigin += Yspacing;
2201     }
2202     /* reserve space for padding below the graph */
2203     im->yimg += Yspacing;
2204     ytr(im,DNAN);
2205
2206     /* Determine where to place the legends onto the image.
2207     ** Adjust im->yimg to match the space requirements.
2208     */
2209     if(leg_place(im)==-1)
2210         return -1;
2211
2212     /* last of three steps: check total height of image */
2213     if (im->yimg < Yvertical) im->yimg = Yvertical;
2214
2215 #if 0
2216     if (Xlegend > im->ximg) {
2217         im->ximg = Xlegend;
2218         /* reposition Pie */
2219     }
2220 #endif
2221
2222     /* The pie is placed in the upper right hand corner,
2223     ** just below the title (if any) and with sufficient
2224     ** padding.
2225     */
2226     if (elements) {
2227         im->pie_x = im->ximg - Xspacing - Xpie/2;
2228         im->pie_y = im->yorigin-Ymain+Ypie/2;
2229     } else {
2230         im->pie_x = im->ximg/2;
2231         im->pie_y = im->yorigin-Ypie/2;
2232     }
2233
2234     return 0;
2235 }
2236
2237 /* draw that picture thing ... */
2238 int
2239 graph_paint(image_desc_t *im, char ***calcpr)
2240 {
2241   int i,ii;
2242   int lazy =     lazy_check(im);
2243   int piechart = 0;
2244   double PieStart=0.0;
2245   FILE  *fo;
2246   gfx_node_t *node;
2247   
2248   double areazero = 0.0;
2249   enum gf_en stack_gf = GF_PRINT;
2250   graph_desc_t *lastgdes = NULL;    
2251
2252   /* if we are lazy and there is nothing to PRINT ... quit now */
2253   if (lazy && im->prt_c==0) return 0;
2254
2255   /* pull the data from the rrd files ... */
2256   
2257   if(data_fetch(im)==-1)
2258     return -1;
2259
2260   /* evaluate VDEF and CDEF operations ... */
2261   if(data_calc(im)==-1)
2262     return -1;
2263   
2264   /* check if we need to draw a piechart */
2265   for(i=0;i<im->gdes_c;i++){
2266     if (im->gdes[i].gf == GF_PART) {
2267       piechart=1;
2268       break;
2269     }
2270   }
2271
2272   /* calculate and PRINT and GPRINT definitions. We have to do it at
2273    * this point because it will affect the length of the legends
2274    * if there are no graph elements we stop here ... 
2275    * if we are lazy, try to quit ... 
2276    */
2277   i=print_calc(im,calcpr);
2278   if(i<0) return -1;
2279   if(((i==0)&&(piechart==0)) || lazy) return 0;
2280
2281   /* If there's only the pie chart to draw, signal this */
2282   if (i==0) piechart=2;
2283   
2284   /* get actual drawing data and find min and max values*/
2285   if(data_proc(im)==-1)
2286     return -1;
2287   
2288   if(!im->logarithmic){si_unit(im);}        /* identify si magnitude Kilo, Mega Giga ? */
2289   
2290   if(!im->rigid && ! im->logarithmic)
2291     expand_range(im);   /* make sure the upper and lower limit are
2292                            sensible values */
2293
2294   if (!calc_horizontal_grid(im))
2295     return -1;
2296
2297   if (im->gridfit)
2298     apply_gridfit(im);
2299
2300
2301 /**************************************************************
2302  *** Calculating sizes and locations became a bit confusing ***
2303  *** so I moved this into a separate function.              ***
2304  **************************************************************/
2305   if(graph_size_location(im,i,piechart)==-1)
2306     return -1;
2307
2308   /* the actual graph is created by going through the individual
2309      graph elements and then drawing them */
2310   
2311   node=gfx_new_area ( im->canvas,
2312                       0, 0,
2313                       im->ximg, 0,
2314                       im->ximg, im->yimg,
2315                       im->graph_col[GRC_BACK]);
2316
2317   gfx_add_point(node,0, im->yimg);
2318
2319   if (piechart != 2) {
2320     node=gfx_new_area ( im->canvas,
2321                       im->xorigin,             im->yorigin, 
2322                       im->xorigin + im->xsize, im->yorigin,
2323                       im->xorigin + im->xsize, im->yorigin-im->ysize,
2324                       im->graph_col[GRC_CANVAS]);
2325   
2326     gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2327
2328     if (im->minval > 0.0)
2329       areazero = im->minval;
2330     if (im->maxval < 0.0)
2331       areazero = im->maxval;
2332     if( !(im->extra_flags & ONLY_GRAPH) )  
2333       axis_paint(im);
2334   }
2335
2336   if (piechart) {
2337     pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2338   }
2339
2340   for(i=0;i<im->gdes_c;i++){
2341     switch(im->gdes[i].gf){
2342     case GF_CDEF:
2343     case GF_VDEF:
2344     case GF_DEF:
2345     case GF_PRINT:
2346     case GF_GPRINT:
2347     case GF_COMMENT:
2348     case GF_HRULE:
2349     case GF_VRULE:
2350     case GF_XPORT:
2351       break;
2352     case GF_TICK:
2353       for (ii = 0; ii < im->xsize; ii++)
2354         {
2355           if (!isnan(im->gdes[i].p_data[ii]) && 
2356               im->gdes[i].p_data[ii] > 0.0)
2357             { 
2358               /* generate a tick */
2359               gfx_new_line(im->canvas, im -> xorigin + ii, 
2360                            im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2361                            im -> xorigin + ii, 
2362                            im -> yorigin,
2363                            1.0,
2364                            im -> gdes[i].col );
2365             }
2366         }
2367       break;
2368     case GF_LINE:
2369     case GF_AREA:
2370       stack_gf = im->gdes[i].gf;
2371     case GF_STACK:          
2372       /* fix data points at oo and -oo */
2373       for(ii=0;ii<im->xsize;ii++){
2374         if (isinf(im->gdes[i].p_data[ii])){
2375           if (im->gdes[i].p_data[ii] > 0) {
2376             im->gdes[i].p_data[ii] = im->maxval ;
2377           } else {
2378             im->gdes[i].p_data[ii] = im->minval ;
2379           }                 
2380           
2381         }
2382       } /* for */
2383       
2384       if (im->gdes[i].col != 0x0){               
2385         /* GF_LINE and friend */
2386         if(stack_gf == GF_LINE ){
2387           node = NULL;
2388           for(ii=1;ii<im->xsize;ii++){
2389             if ( ! isnan(im->gdes[i].p_data[ii-1])
2390                  && ! isnan(im->gdes[i].p_data[ii])){
2391               if (node == NULL){
2392                 node = gfx_new_line(im->canvas,
2393                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2394                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2395                                     im->gdes[i].linewidth,
2396                                     im->gdes[i].col);
2397               } else {
2398                 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2399               }
2400             } else {
2401               node = NULL;
2402             }
2403           }
2404         } else {
2405           int area_start=-1;
2406           node = NULL;
2407           for(ii=1;ii<im->xsize;ii++){
2408             /* open an area */
2409             if ( ! isnan(im->gdes[i].p_data[ii-1])
2410                  && ! isnan(im->gdes[i].p_data[ii])){
2411               if (node == NULL){
2412                 float ybase = 0.0;
2413 /*
2414                 if (im->gdes[i].gf == GF_STACK) {
2415 */
2416                 if ( (im->gdes[i].gf == GF_STACK)
2417                   || (im->gdes[i].stack) ) {
2418
2419                   ybase = ytr(im,lastgdes->p_data[ii-1]);
2420                 } else {
2421                   ybase =  ytr(im,areazero);
2422                 }
2423                 area_start = ii-1;
2424                 node = gfx_new_area(im->canvas,
2425                                     ii-1+im->xorigin,ybase,
2426                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2427                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2428                                     im->gdes[i].col
2429                                     );
2430               } else {
2431                 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2432               }
2433             }
2434
2435             if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2436               /* GF_AREA STACK type*/
2437 /*
2438               if (im->gdes[i].gf == GF_STACK ) {
2439 */
2440               if ( (im->gdes[i].gf == GF_STACK)
2441                 || (im->gdes[i].stack) ) {
2442                 int iii;
2443                 for (iii=ii-1;iii>area_start;iii--){
2444                   gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2445                 }
2446               } else {
2447                 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2448               };
2449               node=NULL;
2450             };
2451           }             
2452         } /* else GF_LINE */
2453       } /* if color != 0x0 */
2454       /* make sure we do not run into trouble when stacking on NaN */
2455       for(ii=0;ii<im->xsize;ii++){
2456         if (isnan(im->gdes[i].p_data[ii])) {
2457           if (lastgdes && (im->gdes[i].gf == GF_STACK)) {
2458             im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2459           } else {
2460             im->gdes[i].p_data[ii] =  ytr(im,areazero);
2461           }
2462         }
2463       } 
2464       lastgdes = &(im->gdes[i]);                         
2465       break;
2466     case GF_PART:
2467       if(isnan(im->gdes[i].yrule)) /* fetch variable */
2468         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2469      
2470       if (finite(im->gdes[i].yrule)) {  /* even the fetched var can be NaN */
2471         pie_part(im,im->gdes[i].col,
2472                 im->pie_x,im->pie_y,im->piesize*0.4,
2473                 M_PI*2.0*PieStart/100.0,
2474                 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2475         PieStart += im->gdes[i].yrule;
2476       }
2477       break;
2478     } /* switch */
2479   }
2480   if (piechart==2) {
2481     im->draw_x_grid=0;
2482     im->draw_y_grid=0;
2483   }
2484   /* grid_paint also does the text */
2485   if( !(im->extra_flags & ONLY_GRAPH) )  
2486     grid_paint(im);
2487   
2488   /* the RULES are the last thing to paint ... */
2489   for(i=0;i<im->gdes_c;i++){    
2490     
2491     switch(im->gdes[i].gf){
2492     case GF_HRULE:
2493       if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2494         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2495       };
2496       if(im->gdes[i].yrule >= im->minval
2497          && im->gdes[i].yrule <= im->maxval)
2498         gfx_new_line(im->canvas,
2499                      im->xorigin,ytr(im,im->gdes[i].yrule),
2500                      im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2501                      1.0,im->gdes[i].col); 
2502       break;
2503     case GF_VRULE:
2504       if(im->gdes[i].xrule == 0) { /* fetch variable */
2505         im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2506       };
2507       if(im->gdes[i].xrule >= im->start
2508          && im->gdes[i].xrule <= im->end)
2509         gfx_new_line(im->canvas,
2510                      xtr(im,im->gdes[i].xrule),im->yorigin,
2511                      xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2512                      1.0,im->gdes[i].col); 
2513       break;
2514     default:
2515       break;
2516     }
2517   }
2518
2519   
2520   if (strcmp(im->graphfile,"-")==0) {
2521     fo = im->graphhandle ? im->graphhandle : stdout;
2522 #ifdef WIN32
2523     /* Change translation mode for stdout to BINARY */
2524     _setmode( _fileno( fo ), O_BINARY );
2525 #endif
2526   } else {
2527     if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2528       rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2529                     rrd_strerror(errno));
2530       return (-1);
2531     }
2532   }
2533   gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2534   if (strcmp(im->graphfile,"-") != 0)
2535     fclose(fo);
2536   return 0;
2537 }
2538
2539
2540 /*****************************************************
2541  * graph stuff 
2542  *****************************************************/
2543
2544 int
2545 gdes_alloc(image_desc_t *im){
2546
2547     unsigned long def_step = (im->end-im->start)/im->xsize;
2548     
2549     if (im->step > def_step) /* step can be increassed ... no decreassed */
2550       def_step = im->step;
2551
2552     im->gdes_c++;
2553     
2554     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2555                                            * sizeof(graph_desc_t)))==NULL){
2556         rrd_set_error("realloc graph_descs");
2557         return -1;
2558     }
2559
2560
2561     im->gdes[im->gdes_c-1].step=def_step; 
2562     im->gdes[im->gdes_c-1].stack=0;
2563     im->gdes[im->gdes_c-1].debug=0;
2564     im->gdes[im->gdes_c-1].start=im->start; 
2565     im->gdes[im->gdes_c-1].end=im->end; 
2566     im->gdes[im->gdes_c-1].vname[0]='\0'; 
2567     im->gdes[im->gdes_c-1].data=NULL;
2568     im->gdes[im->gdes_c-1].ds_namv=NULL;
2569     im->gdes[im->gdes_c-1].data_first=0;
2570     im->gdes[im->gdes_c-1].p_data=NULL;
2571     im->gdes[im->gdes_c-1].rpnp=NULL;
2572     im->gdes[im->gdes_c-1].col = 0x0;
2573     im->gdes[im->gdes_c-1].legend[0]='\0';
2574     im->gdes[im->gdes_c-1].rrd[0]='\0';
2575     im->gdes[im->gdes_c-1].ds=-1;    
2576     im->gdes[im->gdes_c-1].p_data=NULL;    
2577     im->gdes[im->gdes_c-1].yrule=DNAN;
2578     im->gdes[im->gdes_c-1].xrule=0;
2579     return 0;
2580 }
2581
2582 /* copies input untill the first unescaped colon is found
2583    or until input ends. backslashes have to be escaped as well */
2584 int
2585 scan_for_col(char *input, int len, char *output)
2586 {
2587     int inp,outp=0;
2588     for (inp=0; 
2589          inp < len &&
2590            input[inp] != ':' &&
2591            input[inp] != '\0';
2592          inp++){
2593       if (input[inp] == '\\' &&
2594           input[inp+1] != '\0' && 
2595           (input[inp+1] == '\\' ||
2596            input[inp+1] == ':')){
2597         output[outp++] = input[++inp];
2598       }
2599       else {
2600         output[outp++] = input[inp];
2601       }
2602     }
2603     output[outp] = '\0';
2604     return inp;
2605 }
2606 /* Some surgery done on this function, it became ridiculously big.
2607 ** Things moved:
2608 ** - initializing     now in rrd_graph_init()
2609 ** - options parsing  now in rrd_graph_options()
2610 ** - script parsing   now in rrd_graph_script()
2611 */
2612 int 
2613 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream)
2614 {
2615     image_desc_t   im;
2616             
2617     rrd_graph_init(&im);
2618     im.graphhandle = stream;
2619     
2620     rrd_graph_options(argc,argv,&im);
2621     if (rrd_test_error()) {
2622         im_free(&im);
2623         return -1;
2624     }
2625     
2626     if (strlen(argv[optind])>=MAXPATH) {
2627         rrd_set_error("filename (including path) too long");
2628         im_free(&im);
2629         return -1;
2630     }
2631     strncpy(im.graphfile,argv[optind],MAXPATH-1);
2632     im.graphfile[MAXPATH-1]='\0';
2633
2634     rrd_graph_script(argc,argv,&im);
2635     if (rrd_test_error()) {
2636         im_free(&im);
2637         return -1;
2638     }
2639
2640     /* Everything is now read and the actual work can start */
2641
2642     (*prdata)=NULL;
2643     if (graph_paint(&im,prdata)==-1){
2644         im_free(&im);
2645         return -1;
2646     }
2647
2648     /* The image is generated and needs to be output.
2649     ** Also, if needed, print a line with information about the image.
2650     */
2651
2652     *xsize=im.ximg;
2653     *ysize=im.yimg;
2654     if (im.imginfo) {
2655         char *filename;
2656         if (!(*prdata)) {
2657             /* maybe prdata is not allocated yet ... lets do it now */
2658             if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2659                 rrd_set_error("malloc imginfo");
2660                 return -1; 
2661             };
2662         }
2663         if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2664          ==NULL){
2665             rrd_set_error("malloc imginfo");
2666             return -1;
2667         }
2668         filename=im.graphfile+strlen(im.graphfile);
2669         while(filename > im.graphfile) {
2670             if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2671             filename--;
2672         }
2673
2674         sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2675     }
2676     im_free(&im);
2677     return 0;
2678 }
2679
2680 void
2681 rrd_graph_init(image_desc_t *im)
2682 {
2683     unsigned int i;
2684
2685 #ifdef HAVE_TZSET
2686     tzset();
2687 #endif
2688 #ifdef HAVE_SETLOCALE
2689     setlocale(LC_TIME,"");
2690 #endif
2691
2692     im->xlab_user.minsec = -1;
2693     im->ximg=0;
2694     im->yimg=0;
2695     im->xsize = 400;
2696     im->ysize = 100;
2697     im->step = 0;
2698     im->ylegend[0] = '\0';
2699     im->title[0] = '\0';
2700     im->minval = DNAN;
2701     im->maxval = DNAN;    
2702     im->unitsexponent= 9999;
2703     im->extra_flags= 0;
2704     im->rigid = 0;
2705     im->gridfit = 1;
2706     im->imginfo = NULL;
2707     im->lazy = 0;
2708     im->logarithmic = 0;
2709     im->ygridstep = DNAN;
2710     im->draw_x_grid = 1;
2711     im->draw_y_grid = 1;
2712     im->base = 1000;
2713     im->prt_c = 0;
2714     im->gdes_c = 0;
2715     im->gdes = NULL;
2716     im->canvas = gfx_new_canvas();
2717     im->grid_dash_on = 1;
2718     im->grid_dash_off = 1;
2719
2720     for(i=0;i<DIM(graph_col);i++)
2721         im->graph_col[i]=graph_col[i];
2722 #ifdef WIN32
2723     {
2724     char *windir; 
2725     windir = getenv("windir");
2726     /* %windir% is something like D:\windows or C:\winnt */
2727     if (windir != NULL) {
2728         strcpy(rrd_win_default_font,windir);
2729         strcat(rrd_win_default_font,"\\fonts\\cour.ttf");
2730         for(i=0;i<DIM(text_prop);i++)
2731            text_prop[i].font = rrd_win_default_font;
2732     }
2733     }
2734 #endif
2735     for(i=0;i<DIM(text_prop);i++){        
2736       im->text_prop[i].size = text_prop[i].size;
2737       im->text_prop[i].font = text_prop[i].font;
2738     }
2739 }
2740
2741 void
2742 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2743 {
2744     int                 stroff;    
2745     char                *parsetime_error = NULL;
2746     char                scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2747     time_t              start_tmp=0,end_tmp=0;
2748     long                long_tmp;
2749     struct rrd_time_value       start_tv, end_tv;
2750     gfx_color_t         color;
2751
2752     parsetime("end-24h", &start_tv);
2753     parsetime("now", &end_tv);
2754
2755     while (1){
2756         static struct option long_options[] =
2757         {
2758             {"start",      required_argument, 0,  's'},
2759             {"end",        required_argument, 0,  'e'},
2760             {"x-grid",     required_argument, 0,  'x'},
2761             {"y-grid",     required_argument, 0,  'y'},
2762             {"vertical-label",required_argument,0,'v'},
2763             {"width",      required_argument, 0,  'w'},
2764             {"height",     required_argument, 0,  'h'},
2765             {"interlaced", no_argument,       0,  'i'},
2766             {"upper-limit",required_argument, 0,  'u'},
2767             {"lower-limit",required_argument, 0,  'l'},
2768             {"rigid",      no_argument,       0,  'r'},
2769             {"base",       required_argument, 0,  'b'},
2770             {"logarithmic",no_argument,       0,  'o'},
2771             {"color",      required_argument, 0,  'c'},
2772             {"font",       required_argument, 0,  'n'},
2773             {"title",      required_argument, 0,  't'},
2774             {"imginfo",    required_argument, 0,  'f'},
2775             {"imgformat",  required_argument, 0,  'a'},
2776             {"lazy",       no_argument,       0,  'z'},
2777             {"zoom",       required_argument, 0,  'm'},
2778             {"no-legend",  no_argument,       0,  'g'},
2779            {"only-graph", no_argument,       0,  'j'},
2780             {"alt-y-grid", no_argument,       0,  'Y'},
2781             {"no-minor",   no_argument,       0,  'I'},
2782             {"alt-autoscale", no_argument,    0,  'A'},
2783             {"alt-autoscale-max", no_argument, 0, 'M'},
2784             {"units-exponent",required_argument, 0, 'X'},
2785             {"step",       required_argument, 0,    'S'},
2786             {"no-gridfit", no_argument,       0,   'N'},
2787             {0,0,0,0}};
2788         int option_index = 0;
2789         int opt;
2790
2791
2792         opt = getopt_long(argc, argv, 
2793                          "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjYAMX:S:N",
2794                           long_options, &option_index);
2795
2796         if (opt == EOF)
2797             break;
2798         
2799         switch(opt) {
2800         case 'I':
2801             im->extra_flags |= NOMINOR;
2802             break;
2803         case 'Y':
2804             im->extra_flags |= ALTYGRID;
2805             break;
2806         case 'A':
2807             im->extra_flags |= ALTAUTOSCALE;
2808             break;
2809         case 'M':
2810             im->extra_flags |= ALTAUTOSCALE_MAX;
2811             break;
2812        case 'j':
2813            im->extra_flags |= ONLY_GRAPH;
2814            break;
2815         case 'g':
2816             im->extra_flags |= NOLEGEND;
2817             break;
2818         case 'X':
2819             im->unitsexponent = atoi(optarg);
2820             break;
2821         case 'S':
2822             im->step =  atoi(optarg);
2823             break;
2824         case 262:
2825             im->gridfit = 0;
2826             break;
2827         case 's':
2828             if ((parsetime_error = parsetime(optarg, &start_tv))) {
2829                 rrd_set_error( "start time: %s", parsetime_error );
2830                 return;
2831             }
2832             break;
2833         case 'e':
2834             if ((parsetime_error = parsetime(optarg, &end_tv))) {
2835                 rrd_set_error( "end time: %s", parsetime_error );
2836                 return;
2837             }
2838             break;
2839         case 'x':
2840             if(strcmp(optarg,"none") == 0){
2841               im->draw_x_grid=0;
2842               break;
2843             };
2844                 
2845             if(sscanf(optarg,
2846                       "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2847                       scan_gtm,
2848                       &im->xlab_user.gridst,
2849                       scan_mtm,
2850                       &im->xlab_user.mgridst,
2851                       scan_ltm,
2852                       &im->xlab_user.labst,
2853                       &im->xlab_user.precis,
2854                       &stroff) == 7 && stroff != 0){
2855                 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2856                 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2857                     rrd_set_error("unknown keyword %s",scan_gtm);
2858                     return;
2859                 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2860                     rrd_set_error("unknown keyword %s",scan_mtm);
2861                     return;
2862                 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2863                     rrd_set_error("unknown keyword %s",scan_ltm);
2864                     return;
2865                 } 
2866                 im->xlab_user.minsec = 1;
2867                 im->xlab_user.stst = im->xlab_form;
2868             } else {
2869                 rrd_set_error("invalid x-grid format");
2870                 return;
2871             }
2872             break;
2873         case 'y':
2874
2875             if(strcmp(optarg,"none") == 0){
2876               im->draw_y_grid=0;
2877               break;
2878             };
2879
2880             if(sscanf(optarg,
2881                       "%lf:%d",
2882                       &im->ygridstep,
2883                       &im->ylabfact) == 2) {
2884                 if(im->ygridstep<=0){
2885                     rrd_set_error("grid step must be > 0");
2886                     return;
2887                 } else if (im->ylabfact < 1){
2888                     rrd_set_error("label factor must be > 0");
2889                     return;
2890                 } 
2891             } else {
2892                 rrd_set_error("invalid y-grid format");
2893                 return;
2894             }
2895             break;
2896         case 'v':
2897             strncpy(im->ylegend,optarg,150);
2898             im->ylegend[150]='\0';
2899             break;
2900         case 'u':
2901             im->maxval = atof(optarg);
2902             break;
2903         case 'l':
2904             im->minval = atof(optarg);
2905             break;
2906         case 'b':
2907             im->base = atol(optarg);
2908             if(im->base != 1024 && im->base != 1000 ){
2909                 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2910                 return;
2911             }
2912             break;
2913         case 'w':
2914             long_tmp = atol(optarg);
2915             if (long_tmp < 10) {
2916                 rrd_set_error("width below 10 pixels");
2917                 return;
2918             }
2919             im->xsize = long_tmp;
2920             break;
2921         case 'h':
2922             long_tmp = atol(optarg);
2923             if (long_tmp < 10) {
2924                 rrd_set_error("height below 10 pixels");
2925                 return;
2926             }
2927             im->ysize = long_tmp;
2928             break;
2929         case 'i':
2930             im->canvas->interlaced = 1;
2931             break;
2932         case 'r':
2933             im->rigid = 1;
2934             break;
2935         case 'f':
2936             im->imginfo = optarg;
2937             break;
2938         case 'a':
2939             if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2940                 rrd_set_error("unsupported graphics format '%s'",optarg);
2941                 return;
2942             }
2943             break;
2944         case 'z':
2945             im->lazy = 1;
2946             break;
2947         case 'o':
2948             im->logarithmic = 1;
2949             if (isnan(im->minval))
2950                 im->minval=1;
2951             break;
2952         case 'c':
2953             if(sscanf(optarg,
2954                       "%10[A-Z]#%8lx",
2955                       col_nam,&color) == 2){
2956                 int ci;
2957                 if((ci=grc_conv(col_nam)) != -1){
2958                     im->graph_col[ci]=color;
2959                 }  else {
2960                   rrd_set_error("invalid color name '%s'",col_nam);
2961                 }
2962             } else {
2963                 rrd_set_error("invalid color def format");
2964                 return;
2965             }
2966             break;        
2967         case 'n':{
2968                         /* originally this used char *prop = "" and
2969                         ** char *font = "dummy" however this results
2970                         ** in a SEG fault, at least on RH7.1
2971                         **
2972                         ** The current implementation isn't proper
2973                         ** either, font is never freed and prop uses
2974                         ** a fixed width string
2975                         */
2976             char prop[100];
2977             double size = 1;
2978             char *font;
2979
2980             font=malloc(255);
2981             if(sscanf(optarg,
2982                                 "%10[A-Z]:%lf:%s",
2983                                 prop,&size,font) == 3){
2984                 int sindex;
2985                 if((sindex=text_prop_conv(prop)) != -1){
2986                     im->text_prop[sindex].size=size;              
2987                     im->text_prop[sindex].font=font;
2988                     if (sindex==0) { /* the default */
2989                         im->text_prop[TEXT_PROP_TITLE].size=size;
2990                         im->text_prop[TEXT_PROP_TITLE].font=font;
2991                         im->text_prop[TEXT_PROP_AXIS].size=size;
2992                         im->text_prop[TEXT_PROP_AXIS].font=font;
2993                         im->text_prop[TEXT_PROP_UNIT].size=size;
2994                         im->text_prop[TEXT_PROP_UNIT].font=font;
2995                         im->text_prop[TEXT_PROP_LEGEND].size=size;
2996                         im->text_prop[TEXT_PROP_LEGEND].font=font;
2997                     }
2998                 } else {
2999                     rrd_set_error("invalid fonttag '%s'",prop);
3000                     return;
3001                 }
3002             } else {
3003                 rrd_set_error("invalid text property format");
3004                 return;
3005             }
3006             break;          
3007         }
3008         case 'm':
3009             im->canvas->zoom = atof(optarg);
3010             if (im->canvas->zoom <= 0.0) {
3011                 rrd_set_error("zoom factor must be > 0");
3012                 return;
3013             }
3014           break;
3015         case 't':
3016             strncpy(im->title,optarg,150);
3017             im->title[150]='\0';
3018             break;
3019
3020         case '?':
3021             if (optopt != 0)
3022                 rrd_set_error("unknown option '%c'", optopt);
3023             else
3024                 rrd_set_error("unknown option '%s'",argv[optind-1]);
3025             return;
3026         }
3027     }
3028     
3029     if (optind >= argc) {
3030        rrd_set_error("missing filename");
3031        return;
3032     }
3033
3034     if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
3035         rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");    
3036         return;
3037     }
3038
3039     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3040         /* error string is set in parsetime.c */
3041         return;
3042     }  
3043     
3044     if (start_tmp < 3600*24*365*10){
3045         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3046         return;
3047     }
3048     
3049     if (end_tmp < start_tmp) {
3050         rrd_set_error("start (%ld) should be less than end (%ld)", 
3051                start_tmp, end_tmp);
3052         return;
3053     }
3054     
3055     im->start = start_tmp;
3056     im->end = end_tmp;
3057 }
3058
3059 int
3060 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3061 {
3062     if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3063         rrd_set_error("Unknown variable '%s' in %s",varname,err);
3064         return -1;
3065     }
3066     return 0;
3067 }
3068 int
3069 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3070 {
3071     char *color;
3072     graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3073
3074     color=strstr(var,"#");
3075     if (color==NULL) {
3076         if (optional==0) {
3077             rrd_set_error("Found no color in %s",err);
3078             return 0;
3079         }
3080         return 0;
3081     } else {
3082         int n=0;
3083         char *rest;
3084         gfx_color_t    col;
3085
3086         rest=strstr(color,":");
3087         if (rest!=NULL)
3088             n=rest-color;
3089         else
3090             n=strlen(color);
3091
3092         switch (n) {
3093             case 7:
3094                 sscanf(color,"#%6lx%n",&col,&n);
3095                 col = (col << 8) + 0xff /* shift left by 8 */;
3096                 if (n!=7) rrd_set_error("Color problem in %s",err);
3097                 break;
3098             case 9:
3099                 sscanf(color,"#%8lx%n",&col,&n);
3100                 if (n==9) break;
3101             default:
3102                 rrd_set_error("Color problem in %s",err);
3103         }
3104         if (rrd_test_error()) return 0;
3105         gdp->col = col;
3106         return n;
3107     }
3108 }
3109 int
3110 rrd_graph_legend(graph_desc_t *gdp, char *line)
3111 {
3112     int i;
3113
3114     i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3115
3116     return (strlen(&line[i])==0);
3117 }
3118
3119
3120 int bad_format(char *fmt) {
3121     char *ptr;
3122     int n=0;
3123     ptr = fmt;
3124     while (*ptr != '\0')
3125         if (*ptr++ == '%') {
3126  
3127              /* line cannot end with percent char */
3128              if (*ptr == '\0') return 1;
3129  
3130              /* '%s', '%S' and '%%' are allowed */
3131              if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3132
3133              /* or else '% 6.2lf' and such are allowed */
3134              else {
3135    
3136                  /* optional padding character */
3137                  if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3138   
3139                  /* This should take care of 'm.n' with all three optional */
3140                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3141                  if (*ptr == '.') ptr++;
3142                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3143   
3144                  /* Either 'le', 'lf' or 'lg' must follow here */
3145                  if (*ptr++ != 'l') return 1;
3146                  if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3147                  else return 1;
3148                  n++;
3149             }
3150          }
3151       
3152       return (n!=1); 
3153 }
3154
3155
3156 int
3157 vdef_parse(gdes,str)
3158 struct graph_desc_t *gdes;
3159 char *str;
3160 {
3161     /* A VDEF currently is either "func" or "param,func"
3162      * so the parsing is rather simple.  Change if needed.
3163      */
3164     double      param;
3165     char        func[30];
3166     int         n;
3167     
3168     n=0;
3169     sscanf(str,"%le,%29[A-Z]%n",&param,func,&n);
3170     if (n==strlen(str)) { /* matched */
3171         ;
3172     } else {
3173         n=0;
3174         sscanf(str,"%29[A-Z]%n",func,&n);
3175         if (n==strlen(str)) { /* matched */
3176             param=DNAN;
3177         } else {
3178             rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3179                 ,str
3180                 ,gdes->vname
3181                 );
3182             return -1;
3183         }
3184     }
3185     if          (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3186     else if     (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3187     else if     (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3188     else if     (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3189     else if     (!strcmp("TOTAL",  func)) gdes->vf.op = VDEF_TOTAL;
3190     else if     (!strcmp("FIRST",  func)) gdes->vf.op = VDEF_FIRST;
3191     else if     (!strcmp("LAST",   func)) gdes->vf.op = VDEF_LAST;
3192     else {
3193         rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3194             ,func
3195             ,gdes->vname
3196             );
3197         return -1;
3198     };
3199
3200     switch (gdes->vf.op) {
3201         case VDEF_PERCENT:
3202             if (isnan(param)) { /* no parameter given */
3203                 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3204                     ,func
3205                     ,gdes->vname
3206                     );
3207                 return -1;
3208             };
3209             if (param>=0.0 && param<=100.0) {
3210                 gdes->vf.param = param;
3211                 gdes->vf.val   = DNAN;  /* undefined */
3212                 gdes->vf.when  = 0;     /* undefined */
3213             } else {
3214                 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3215                     ,param
3216                     ,gdes->vname
3217                     );
3218                 return -1;
3219             };
3220             break;
3221         case VDEF_MAXIMUM:
3222         case VDEF_AVERAGE:
3223         case VDEF_MINIMUM:
3224         case VDEF_TOTAL:
3225         case VDEF_FIRST:
3226         case VDEF_LAST:
3227             if (isnan(param)) {
3228                 gdes->vf.param = DNAN;
3229                 gdes->vf.val   = DNAN;
3230                 gdes->vf.when  = 0;
3231             } else {
3232                 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3233                     ,func
3234                     ,gdes->vname
3235                     );
3236                 return -1;
3237             };
3238             break;
3239     };
3240     return 0;
3241 }
3242
3243
3244 int
3245 vdef_calc(im,gdi)
3246 image_desc_t *im;
3247 int gdi;
3248 {
3249     graph_desc_t        *src,*dst;
3250     rrd_value_t         *data;
3251     long                step,steps;
3252
3253     dst = &im->gdes[gdi];
3254     src = &im->gdes[dst->vidx];
3255     data = src->data + src->ds;
3256     steps = (src->end - src->start) / src->step;
3257
3258 #if 0
3259 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3260     ,src->start
3261     ,src->end
3262     ,steps
3263     );
3264 #endif
3265
3266     switch (dst->vf.op) {
3267         case VDEF_PERCENT: {
3268                 rrd_value_t *   array;
3269                 int             field;
3270
3271
3272                 if ((array = malloc(steps*sizeof(double)))==NULL) {
3273                     rrd_set_error("malloc VDEV_PERCENT");
3274                     return -1;
3275                 }
3276                 for (step=0;step < steps; step++) {
3277                     array[step]=data[step*src->ds_cnt];
3278                 }
3279                 qsort(array,step,sizeof(double),vdef_percent_compar);
3280
3281                 field = (steps-1)*dst->vf.param/100;
3282                 dst->vf.val  = array[field];
3283                 dst->vf.when = 0;       /* no time component */
3284                 free(array);
3285 #if 0
3286 for(step=0;step<steps;step++)
3287 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3288 #endif
3289             }
3290             break;
3291         case VDEF_MAXIMUM:
3292             step=0;
3293             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3294             if (step == steps) {
3295                 dst->vf.val  = DNAN;
3296                 dst->vf.when = 0;
3297             } else {
3298                 dst->vf.val  = data[step*src->ds_cnt];
3299                 dst->vf.when = src->start + (step+1)*src->step;
3300             }
3301             while (step != steps) {
3302                 if (finite(data[step*src->ds_cnt])) {
3303                     if (data[step*src->ds_cnt] > dst->vf.val) {
3304                         dst->vf.val  = data[step*src->ds_cnt];
3305                         dst->vf.when = src->start + (step+1)*src->step;
3306                     }
3307                 }
3308                 step++;
3309             }
3310             break;
3311         case VDEF_TOTAL:
3312         case VDEF_AVERAGE: {
3313             int cnt=0;
3314             double sum=0.0;
3315             for (step=0;step<steps;step++) {
3316                 if (finite(data[step*src->ds_cnt])) {
3317                     sum += data[step*src->ds_cnt];
3318                     cnt ++;
3319                 };
3320             }
3321             if (cnt) {
3322                 if (dst->vf.op == VDEF_TOTAL) {
3323                     dst->vf.val  = sum*src->step;
3324                     dst->vf.when = cnt*src->step;       /* not really "when" */
3325                 } else {
3326                     dst->vf.val = sum/cnt;
3327                     dst->vf.when = 0;   /* no time component */
3328                 };
3329             } else {
3330                 dst->vf.val  = DNAN;
3331                 dst->vf.when = 0;
3332             }
3333             }
3334             break;
3335         case VDEF_MINIMUM:
3336             step=0;
3337             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3338             if (step == steps) {
3339                 dst->vf.val  = DNAN;
3340                 dst->vf.when = 0;
3341             } else {
3342                 dst->vf.val  = data[step*src->ds_cnt];
3343                 dst->vf.when = src->start + (step+1)*src->step;
3344             }
3345             while (step != steps) {
3346                 if (finite(data[step*src->ds_cnt])) {
3347                     if (data[step*src->ds_cnt] < dst->vf.val) {
3348                         dst->vf.val  = data[step*src->ds_cnt];
3349                         dst->vf.when = src->start + (step+1)*src->step;
3350                     }
3351                 }
3352                 step++;
3353             }
3354             break;
3355         case VDEF_FIRST:
3356             /* The time value returned here is one step before the
3357              * actual time value.  This is the start of the first
3358              * non-NaN interval.
3359              */
3360             step=0;
3361             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3362             if (step == steps) { /* all entries were NaN */
3363                 dst->vf.val  = DNAN;
3364                 dst->vf.when = 0;
3365             } else {
3366                 dst->vf.val  = data[step*src->ds_cnt];
3367                 dst->vf.when = src->start + step*src->step;
3368             }
3369             break;
3370         case VDEF_LAST:
3371             /* The time value returned here is the
3372              * actual time value.  This is the end of the last
3373              * non-NaN interval.
3374              */
3375             step=steps-1;
3376             while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3377             if (step < 0) { /* all entries were NaN */
3378                 dst->vf.val  = DNAN;
3379                 dst->vf.when = 0;
3380             } else {
3381                 dst->vf.val  = data[step*src->ds_cnt];
3382                 dst->vf.when = src->start + (step+1)*src->step;
3383             }
3384             break;
3385     }
3386     return 0;
3387 }
3388
3389 /* NaN < -INF < finite_values < INF */
3390 int
3391 vdef_percent_compar(a,b)
3392 const void *a,*b;
3393 {
3394     /* Equality is not returned; this doesn't hurt except
3395      * (maybe) for a little performance.
3396      */
3397
3398     /* First catch NaN values. They are smallest */
3399     if (isnan( *(double *)a )) return -1;
3400     if (isnan( *(double *)b )) return  1;
3401
3402     /* NaN doesn't reach this part so INF and -INF are extremes.
3403      * The sign from isinf() is compatible with the sign we return
3404      */
3405     if (isinf( *(double *)a )) return isinf( *(double *)a );
3406     if (isinf( *(double *)b )) return isinf( *(double *)b );
3407
3408     /* If we reach this, both values must be finite */
3409     if ( *(double *)a < *(double *)b ) return -1; else return 1;
3410 }