when moving the data pointers we should take into account how fahr off they
[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      { 10.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;(long int)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     int i,ii;
685     int         skip;
686
687     /* pull the data from the log files ... */
688     for (i=0;i< (int)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<(int)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                                 * ((im->gdes[gdi].start - im->gdes[ptr].start) / im->gdes[ptr].step);
918                         }
919                      }
920                 }
921         
922
923                 if(steparray == NULL){
924                     rrd_set_error("rpn expressions without DEF"
925                                 " or CDEF variables are not supported");
926                     rpnstack_free(&rpnstack);
927                     return -1;    
928                 }
929                 steparray[stepcnt]=0;
930                 /* Now find the resulting step.  All steps in all
931                  * used RRAs have to be visited
932                  */
933                 im->gdes[gdi].step = lcd(steparray);
934                 free(steparray);
935                 if((im->gdes[gdi].data = malloc((
936                                 (im->gdes[gdi].end-im->gdes[gdi].start) 
937                                     / im->gdes[gdi].step)
938                                     * sizeof(double)))==NULL){
939                     rrd_set_error("malloc im->gdes[gdi].data");
940                     rpnstack_free(&rpnstack);
941                     return -1;
942                 }
943         
944                 /* Step through the new cdef results array and
945                  * calculate the values
946                  */
947                 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
948                                 now<=im->gdes[gdi].end;
949                                 now += im->gdes[gdi].step)
950                 {
951                     rpnp_t  *rpnp = im -> gdes[gdi].rpnp;
952
953                     /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
954                      * in this case we are advancing by timesteps;
955                      * we use the fact that time_t is a synonym for long
956                      */
957                     if (rpn_calc(rpnp,&rpnstack,(long) now, 
958                                 im->gdes[gdi].data,++dataidx) == -1) {
959                         /* rpn_calc sets the error string */
960                         rpnstack_free(&rpnstack); 
961                         return -1;
962                     } 
963                 } /* enumerate over time steps within a CDEF */
964                 break;
965             default:
966                 continue;
967         }
968     } /* enumerate over CDEFs */
969     rpnstack_free(&rpnstack);
970     return 0;
971 }
972
973 /* massage data so, that we get one value for each x coordinate in the graph */
974 int
975 data_proc( image_desc_t *im ){
976     long i,ii;
977     double pixstep = (double)(im->end-im->start)
978         /(double)im->xsize; /* how much time 
979                                passes in one pixel */
980     double paintval;
981     double minval=DNAN,maxval=DNAN;
982     
983     unsigned long gr_time;    
984
985     /* memory for the processed data */
986     for(i=0;i<im->gdes_c;i++) {
987         if((im->gdes[i].gf==GF_LINE) ||
988                 (im->gdes[i].gf==GF_AREA) ||
989                 (im->gdes[i].gf==GF_TICK) ||
990                 (im->gdes[i].gf==GF_STACK)) {
991             if((im->gdes[i].p_data = malloc((im->xsize +1)
992                                         * sizeof(rrd_value_t)))==NULL){
993                 rrd_set_error("malloc data_proc");
994                 return -1;
995             }
996         }
997     }
998
999     for (i=0;i<im->xsize;i++) { /* for each pixel */
1000         long vidx;
1001         gr_time = im->start+pixstep*i; /* time of the current step */
1002         paintval=0.0;
1003         
1004         for (ii=0;ii<im->gdes_c;ii++) {
1005             double value;
1006             switch (im->gdes[ii].gf) {
1007                 case GF_LINE:
1008                 case GF_AREA:
1009                 case GF_TICK:
1010                     if (!im->gdes[ii].stack)
1011                         paintval = 0.0;
1012                 case GF_STACK:
1013                     value = im->gdes[ii].yrule;
1014                     if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1015                         /* The time of the data doesn't necessarily match
1016                         ** the time of the graph. Beware.
1017                         */
1018                         vidx = im->gdes[ii].vidx;
1019                         if (    ((long int)gr_time >= (long int)im->gdes[vidx].start) &&
1020                                 ((long int)gr_time <= (long int)im->gdes[vidx].end) ) {
1021                             value = im->gdes[vidx].data[
1022                                 (unsigned long) floor(
1023                                     (double)(gr_time - im->gdes[vidx].start)
1024                                                 / im->gdes[vidx].step)
1025                                 * im->gdes[vidx].ds_cnt
1026                                 + im->gdes[vidx].ds
1027                             ];
1028                         } else {
1029                             value = DNAN;
1030                         }
1031                     };
1032
1033                     if (! isnan(value)) {
1034                         paintval += value;
1035                         im->gdes[ii].p_data[i] = paintval;
1036                         /* GF_TICK: the data values are not
1037                         ** relevant for min and max
1038                         */
1039                         if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1040                             if (isnan(minval) || paintval <  minval)
1041                                 minval = paintval;
1042                             if (isnan(maxval) || paintval >  maxval)
1043                                 maxval = paintval;
1044                         }
1045                     } else {
1046                         im->gdes[ii].p_data[i] = DNAN;
1047                     }
1048                     break;
1049                 default:
1050                     break;
1051             }
1052         }
1053     }
1054
1055     /* if min or max have not been asigned a value this is because
1056        there was no data in the graph ... this is not good ...
1057        lets set these to dummy values then ... */
1058
1059     if (isnan(minval)) minval = 0.0;
1060     if (isnan(maxval)) maxval = 1.0;
1061     
1062     /* adjust min and max values */
1063     if (isnan(im->minval) 
1064         /* don't adjust low-end with log scale */
1065         || ((!im->logarithmic && !im->rigid) && im->minval > minval)
1066         )
1067         im->minval = minval;
1068     if (isnan(im->maxval) 
1069         || (!im->rigid && im->maxval < maxval)
1070         ) {
1071         if (im->logarithmic)
1072             im->maxval = maxval * 1.1;
1073         else
1074             im->maxval = maxval;
1075     }
1076     /* make sure min is smaller than max */
1077     if (im->minval > im->maxval) {
1078             im->minval = 0.99 * im->maxval;
1079     }
1080                       
1081     /* make sure min and max are not equal */
1082     if (im->minval == im->maxval) {
1083         im->maxval *= 1.01; 
1084         if (! im->logarithmic) {
1085             im->minval *= 0.99;
1086         }
1087         /* make sure min and max are not both zero */
1088         if (im->maxval == 0.0) {
1089             im->maxval = 1.0;
1090         }
1091     }
1092     return 0;
1093 }
1094
1095
1096
1097 /* identify the point where the first gridline, label ... gets placed */
1098
1099 time_t
1100 find_first_time(
1101     time_t   start, /* what is the initial time */
1102     enum tmt_en baseint,  /* what is the basic interval */
1103     long     basestep /* how many if these do we jump a time */
1104     )
1105 {
1106     struct tm tm;
1107     localtime_r(&start, &tm);
1108     switch(baseint){
1109     case TMT_SECOND:
1110         tm.tm_sec -= tm.tm_sec % basestep; break;
1111     case TMT_MINUTE: 
1112         tm.tm_sec=0;
1113         tm.tm_min -= tm.tm_min % basestep; 
1114         break;
1115     case TMT_HOUR:
1116         tm.tm_sec=0;
1117         tm.tm_min = 0;
1118         tm.tm_hour -= tm.tm_hour % basestep; break;
1119     case TMT_DAY:
1120         /* we do NOT look at the basestep for this ... */
1121         tm.tm_sec=0;
1122         tm.tm_min = 0;
1123         tm.tm_hour = 0; break;
1124     case TMT_WEEK:
1125         /* we do NOT look at the basestep for this ... */
1126         tm.tm_sec=0;
1127         tm.tm_min = 0;
1128         tm.tm_hour = 0;
1129         tm.tm_mday -= tm.tm_wday -1;    /* -1 because we want the monday */
1130         if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1131         break;
1132     case TMT_MONTH:
1133         tm.tm_sec=0;
1134         tm.tm_min = 0;
1135         tm.tm_hour = 0;
1136         tm.tm_mday = 1;
1137         tm.tm_mon -= tm.tm_mon % basestep; break;
1138
1139     case TMT_YEAR:
1140         tm.tm_sec=0;
1141         tm.tm_min = 0;
1142         tm.tm_hour = 0;
1143         tm.tm_mday = 1;
1144         tm.tm_mon = 0;
1145         tm.tm_year -= (tm.tm_year+1900) % basestep;
1146         
1147     }
1148     return mktime(&tm);
1149 }
1150 /* identify the point where the next gridline, label ... gets placed */
1151 time_t 
1152 find_next_time(
1153     time_t   current, /* what is the initial time */
1154     enum tmt_en baseint,  /* what is the basic interval */
1155     long     basestep /* how many if these do we jump a time */
1156     )
1157 {
1158     struct tm tm;
1159     time_t madetime;
1160     localtime_r(&current, &tm);
1161     do {
1162         switch(baseint){
1163         case TMT_SECOND:
1164             tm.tm_sec += basestep; break;
1165         case TMT_MINUTE: 
1166             tm.tm_min += basestep; break;
1167         case TMT_HOUR:
1168             tm.tm_hour += basestep; break;
1169         case TMT_DAY:
1170             tm.tm_mday += basestep; break;
1171         case TMT_WEEK:
1172             tm.tm_mday += 7*basestep; break;
1173         case TMT_MONTH:
1174             tm.tm_mon += basestep; break;
1175         case TMT_YEAR:
1176             tm.tm_year += basestep;     
1177         }
1178         madetime = mktime(&tm);
1179     } while (madetime == -1); /* this is necessary to skip impssible times
1180                                  like the daylight saving time skips */
1181     return madetime;
1182           
1183 }
1184
1185
1186 /* calculate values required for PRINT and GPRINT functions */
1187
1188 int
1189 print_calc(image_desc_t *im, char ***prdata) 
1190 {
1191     long i,ii,validsteps;
1192     double printval;
1193     time_t printtime;
1194     int graphelement = 0;
1195     long vidx;
1196     int max_ii; 
1197     double magfact = -1;
1198     char *si_symb = "";
1199     char *percent_s;
1200     int prlines = 1;
1201     if (im->imginfo) prlines++;
1202     for(i=0;i<im->gdes_c;i++){
1203         switch(im->gdes[i].gf){
1204         case GF_PRINT:
1205             prlines++;
1206             if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1207                 rrd_set_error("realloc prdata");
1208                 return 0;
1209             }
1210         case GF_GPRINT:
1211             /* PRINT and GPRINT can now print VDEF generated values.
1212              * There's no need to do any calculations on them as these
1213              * calculations were already made.
1214              */
1215             vidx = im->gdes[i].vidx;
1216             if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1217                 printval = im->gdes[vidx].vf.val;
1218                 printtime = im->gdes[vidx].vf.when;
1219             } else { /* need to calculate max,min,avg etcetera */
1220                 max_ii =((im->gdes[vidx].end 
1221                         - im->gdes[vidx].start)
1222                         / im->gdes[vidx].step
1223                         * im->gdes[vidx].ds_cnt);
1224                 printval = DNAN;
1225                 validsteps = 0;
1226                 for(    ii=im->gdes[vidx].ds;
1227                         ii < max_ii;
1228                         ii+=im->gdes[vidx].ds_cnt){
1229                     if (! finite(im->gdes[vidx].data[ii]))
1230                         continue;
1231                     if (isnan(printval)){
1232                         printval = im->gdes[vidx].data[ii];
1233                         validsteps++;
1234                         continue;
1235                     }
1236
1237                     switch (im->gdes[i].cf){
1238                         case CF_HWPREDICT:
1239                         case CF_DEVPREDICT:
1240                         case CF_DEVSEASONAL:
1241                         case CF_SEASONAL:
1242                         case CF_AVERAGE:
1243                             validsteps++;
1244                             printval += im->gdes[vidx].data[ii];
1245                             break;
1246                         case CF_MINIMUM:
1247                             printval = min( printval, im->gdes[vidx].data[ii]);
1248                             break;
1249                         case CF_FAILURES:
1250                         case CF_MAXIMUM:
1251                             printval = max( printval, im->gdes[vidx].data[ii]);
1252                             break;
1253                         case CF_LAST:
1254                             printval = im->gdes[vidx].data[ii];
1255                     }
1256                 }
1257                 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1258                     if (validsteps > 1) {
1259                         printval = (printval / validsteps);
1260                     }
1261                 }
1262             } /* prepare printval */
1263
1264             if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1265                 char ctime_buf[128]; /* PS: for ctime_r, must be >= 26 chars */
1266                 if (im->gdes[i].gf == GF_PRINT){
1267                     (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1268                     sprintf((*prdata)[prlines-2],"%s (%lu)",
1269                                         ctime_r(&printtime,ctime_buf),printtime);
1270                     (*prdata)[prlines-1] = NULL;
1271                 } else {
1272                     sprintf(im->gdes[i].legend,"%s (%lu)",
1273                                         ctime_r(&printtime,ctime_buf),printtime);
1274                     graphelement = 1;
1275                 }
1276             } else {
1277             if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1278                 /* Magfact is set to -1 upon entry to print_calc.  If it
1279                  * is still less than 0, then we need to run auto_scale.
1280                  * Otherwise, put the value into the correct units.  If
1281                  * the value is 0, then do not set the symbol or magnification
1282                  * so next the calculation will be performed again. */
1283                 if (magfact < 0.0) {
1284                     auto_scale(im,&printval,&si_symb,&magfact);
1285                     if (printval == 0.0)
1286                         magfact = -1.0;
1287                 } else {
1288                     printval /= magfact;
1289                 }
1290                 *(++percent_s) = 's';
1291             } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1292                 auto_scale(im,&printval,&si_symb,&magfact);
1293             }
1294
1295             if (im->gdes[i].gf == GF_PRINT){
1296                 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1297                 (*prdata)[prlines-1] = NULL;
1298                 if (bad_format(im->gdes[i].format)) {
1299                         rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1300                         return -1;
1301                 }
1302 #ifdef HAVE_SNPRINTF
1303                 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1304 #else
1305                 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1306 #endif
1307             } else {
1308                 /* GF_GPRINT */
1309
1310                 if (bad_format(im->gdes[i].format)) {
1311                         rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1312                         return -1;
1313                 }
1314 #ifdef HAVE_SNPRINTF
1315                 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1316 #else
1317                 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1318 #endif
1319                 graphelement = 1;
1320             }
1321             }
1322             break;
1323         case GF_LINE:
1324         case GF_AREA:
1325         case GF_TICK:
1326         case GF_STACK:
1327         case GF_HRULE:
1328         case GF_VRULE:
1329             graphelement = 1;
1330             break;
1331         case GF_COMMENT:
1332         case GF_DEF:
1333         case GF_CDEF:       
1334         case GF_VDEF:       
1335         case GF_PART:
1336         case GF_XPORT:
1337             break;
1338         }
1339     }
1340     return graphelement;
1341 }
1342
1343
1344 /* place legends with color spots */
1345 int
1346 leg_place(image_desc_t *im)
1347 {
1348     /* graph labels */
1349     int   interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1350     int   box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1351     int   border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1352     int   fill=0, fill_last;
1353     int   leg_c = 0;
1354     int   leg_x = border, leg_y = im->yimg;
1355     int   leg_cc;
1356     int   glue = 0;
1357     int   i,ii, mark = 0;
1358     char  prt_fctn; /*special printfunctions */
1359     int  *legspace;
1360
1361   if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
1362     if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1363        rrd_set_error("malloc for legspace");
1364        return -1;
1365     }
1366
1367     for(i=0;i<im->gdes_c;i++){
1368         fill_last = fill;
1369         
1370         /* hid legends for rules which are not displayed */
1371         
1372         if (im->gdes[i].gf == GF_HRULE &&
1373             (im->gdes[i].yrule < im->minval || im->gdes[i].yrule > im->maxval))
1374             im->gdes[i].legend[0] = '\0';
1375
1376         if (im->gdes[i].gf == GF_VRULE &&
1377             (im->gdes[i].xrule < im->start || im->gdes[i].xrule > im->end))
1378             im->gdes[i].legend[0] = '\0';
1379
1380         leg_cc = strlen(im->gdes[i].legend);
1381         
1382         /* is there a controle code ant the end of the legend string ? */ 
1383         if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1384             prt_fctn = im->gdes[i].legend[leg_cc-1];
1385             leg_cc -= 2;
1386             im->gdes[i].legend[leg_cc] = '\0';
1387         } else {
1388             prt_fctn = '\0';
1389         }
1390         /* remove exess space */
1391         while (prt_fctn=='g' && 
1392                leg_cc > 0 && 
1393                im->gdes[i].legend[leg_cc-1]==' '){
1394            leg_cc--;
1395            im->gdes[i].legend[leg_cc]='\0';
1396         }
1397         if (leg_cc != 0 ){
1398            legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1399            
1400            if (fill > 0){ 
1401                /* no interleg space if string ends in \g */
1402                fill += legspace[i];
1403             }
1404             if (im->gdes[i].gf != GF_GPRINT && 
1405                 im->gdes[i].gf != GF_COMMENT) { 
1406                 fill += box;       
1407             }
1408            fill += gfx_get_text_width(im->canvas, fill+border,
1409                                       im->text_prop[TEXT_PROP_LEGEND].font,
1410                                       im->text_prop[TEXT_PROP_LEGEND].size,
1411                                       im->tabwidth,
1412                                       im->gdes[i].legend, 0);
1413             leg_c++;
1414         } else {
1415            legspace[i]=0;
1416         }
1417         /* who said there was a special tag ... ?*/
1418         if (prt_fctn=='g') {    
1419            prt_fctn = '\0';
1420         }
1421         if (prt_fctn == '\0') {
1422             if (i == im->gdes_c -1 ) prt_fctn ='l';
1423             
1424             /* is it time to place the legends ? */
1425             if (fill > im->ximg - 2*border){
1426                 if (leg_c > 1) {
1427                     /* go back one */
1428                     i--; 
1429                     fill = fill_last;
1430                     leg_c--;
1431                     prt_fctn = 'j';
1432                 } else {
1433                     prt_fctn = 'l';
1434                 }
1435                 
1436             }
1437         }
1438
1439
1440         if (prt_fctn != '\0'){
1441             leg_x = border;
1442             if (leg_c >= 2 && prt_fctn == 'j') {
1443                 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1444             } else {
1445                 glue = 0;
1446             }
1447             if (prt_fctn =='c') leg_x =  (im->ximg - fill) / 2.0;
1448             if (prt_fctn =='r') leg_x =  im->ximg - fill - border;
1449
1450             for(ii=mark;ii<=i;ii++){
1451                 if(im->gdes[ii].legend[0]=='\0')
1452                     continue;
1453                 im->gdes[ii].leg_x = leg_x;
1454                 im->gdes[ii].leg_y = leg_y;
1455                 leg_x += 
1456                  gfx_get_text_width(im->canvas, leg_x,
1457                                       im->text_prop[TEXT_PROP_LEGEND].font,
1458                                       im->text_prop[TEXT_PROP_LEGEND].size,
1459                                       im->tabwidth,
1460                                       im->gdes[ii].legend, 0) 
1461                    + legspace[ii]
1462                    + glue;
1463                 if (im->gdes[ii].gf != GF_GPRINT && 
1464                     im->gdes[ii].gf != GF_COMMENT) 
1465                     leg_x += box;          
1466             }       
1467             leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1468             if (prt_fctn == 's') leg_y -=  im->text_prop[TEXT_PROP_LEGEND].size*1.2;       
1469             fill = 0;
1470             leg_c = 0;
1471             mark = ii;
1472         }          
1473     }
1474     im->yimg = leg_y;
1475     free(legspace);
1476   }
1477   return 0;
1478 }
1479
1480 /* create a grid on the graph. it determines what to do
1481    from the values of xsize, start and end */
1482
1483 /* the xaxis labels are determined from the number of seconds per pixel
1484    in the requested graph */
1485
1486
1487
1488 int
1489 calc_horizontal_grid(image_desc_t   *im)
1490 {
1491     double   range;
1492     double   scaledrange;
1493     int      pixel,i;
1494     int      gridind;
1495     int      decimals, fractionals;
1496
1497     im->ygrid_scale.labfact=2;
1498     gridind=-1;
1499     range =  im->maxval - im->minval;
1500     scaledrange = range / im->magfact;
1501
1502         /* does the scale of this graph make it impossible to put lines
1503            on it? If so, give up. */
1504         if (isnan(scaledrange)) {
1505                 return 0;
1506         }
1507
1508     /* find grid spaceing */
1509     pixel=1;
1510     if(isnan(im->ygridstep)){
1511         if(im->extra_flags & ALTYGRID) {
1512             /* find the value with max number of digits. Get number of digits */
1513             decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1514             if(decimals <= 0) /* everything is small. make place for zero */
1515                 decimals = 1;
1516             
1517             fractionals = floor(log10(range));
1518             if(fractionals < 0) /* small amplitude. */
1519                 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1520             else
1521                 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1522             im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1523             if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1524                 im->ygrid_scale.gridstep = 0.1;
1525             /* should have at least 5 lines but no more then 15 */
1526             if(range/im->ygrid_scale.gridstep < 5)
1527                 im->ygrid_scale.gridstep /= 10;
1528             if(range/im->ygrid_scale.gridstep > 15)
1529                 im->ygrid_scale.gridstep *= 10;
1530             if(range/im->ygrid_scale.gridstep > 5) {
1531                 im->ygrid_scale.labfact = 1;
1532                 if(range/im->ygrid_scale.gridstep > 8)
1533                     im->ygrid_scale.labfact = 2;
1534             }
1535             else {
1536                 im->ygrid_scale.gridstep /= 5;
1537                 im->ygrid_scale.labfact = 5;
1538             }
1539         }
1540         else {
1541             for(i=0;ylab[i].grid > 0;i++){
1542                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1543                 if (gridind == -1 && pixel > 5) {
1544                     gridind = i;
1545                     break;
1546                 }
1547             }
1548             
1549             for(i=0; i<4;i++) {
1550                if (pixel * ylab[gridind].lfac[i] >=  2 * im->text_prop[TEXT_PROP_AXIS].size) {
1551                   im->ygrid_scale.labfact =  ylab[gridind].lfac[i];
1552                   break;
1553                }                          
1554             } 
1555             
1556             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1557         }
1558     } else {
1559         im->ygrid_scale.gridstep = im->ygridstep;
1560         im->ygrid_scale.labfact = im->ylabfact;
1561     }
1562     return 1;
1563 }
1564
1565 int draw_horizontal_grid(image_desc_t *im)
1566 {
1567     int      i;
1568     double   scaledstep;
1569     char     graph_label[100];
1570     double X0=im->xorigin;
1571     double X1=im->xorigin+im->xsize;
1572    
1573     int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1574     int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1575     scaledstep = im->ygrid_scale.gridstep/im->magfact;
1576     for (i = sgrid; i <= egrid; i++){
1577        double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1578        if ( Y0 >= im->yorigin-im->ysize
1579                  && Y0 <= im->yorigin){       
1580             if(i % im->ygrid_scale.labfact == 0){               
1581                 if (i==0 || im->symbol == ' ') {
1582                     if(scaledstep < 1){
1583                         if(im->extra_flags & ALTYGRID) {
1584                             sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1585                         }
1586                         else {
1587                             sprintf(graph_label,"%4.1f",scaledstep*i);
1588                         }
1589                     } else {
1590                         sprintf(graph_label,"%4.0f",scaledstep*i);
1591                     }
1592                 }else {
1593                     if(scaledstep < 1){
1594                         sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1595                     } else {
1596                         sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1597                     }
1598                 }
1599
1600                gfx_new_text ( im->canvas,
1601                               X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1602                               im->graph_col[GRC_FONT],
1603                               im->text_prop[TEXT_PROP_AXIS].font,
1604                               im->text_prop[TEXT_PROP_AXIS].size,
1605                               im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1606                               graph_label );
1607                gfx_new_dashed_line ( im->canvas,
1608                               X0-2,Y0,
1609                               X1+2,Y0,
1610                               MGRIDWIDTH, im->graph_col[GRC_MGRID],
1611                               im->grid_dash_on, im->grid_dash_off);            
1612                
1613             } else if (!(im->extra_flags & NOMINOR)) {          
1614                gfx_new_dashed_line ( im->canvas,
1615                               X0-1,Y0,
1616                               X1+1,Y0,
1617                               GRIDWIDTH, im->graph_col[GRC_GRID],
1618                               im->grid_dash_on, im->grid_dash_off);            
1619                
1620             }       
1621         }       
1622     } 
1623     return 1;
1624 }
1625
1626 /* logaritmic horizontal grid */
1627 int
1628 horizontal_log_grid(image_desc_t   *im)   
1629 {
1630     double   pixpex;
1631     int      ii,i;
1632     int      minoridx=0, majoridx=0;
1633     char     graph_label[100];
1634     double   X0,X1,Y0;   
1635     double   value, pixperstep, minstep;
1636
1637     /* find grid spaceing */
1638     pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1639
1640         if (isnan(pixpex)) {
1641                 return 0;
1642         }
1643
1644     for(i=0;yloglab[i][0] > 0;i++){
1645         minstep = log10(yloglab[i][0]);
1646         for(ii=1;yloglab[i][ii+1] > 0;ii++){
1647             if(yloglab[i][ii+2]==0){
1648                 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1649                 break;
1650             }
1651         }
1652         pixperstep = pixpex * minstep;
1653         if(pixperstep > 5){minoridx = i;}
1654        if(pixperstep > 2 *  im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1655     }
1656    
1657    X0=im->xorigin;
1658    X1=im->xorigin+im->xsize;
1659     /* paint minor grid */
1660     for (value = pow((double)10, log10(im->minval) 
1661                           - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1662          value  <= im->maxval;
1663          value *= yloglab[minoridx][0]){
1664         if (value < im->minval) continue;
1665         i=0;    
1666         while(yloglab[minoridx][++i] > 0){          
1667            Y0 = ytr(im,value * yloglab[minoridx][i]);
1668            if (Y0 <= im->yorigin - im->ysize) break;
1669            gfx_new_dashed_line ( im->canvas,
1670                           X0-1,Y0,
1671                           X1+1,Y0,
1672                           GRIDWIDTH, im->graph_col[GRC_GRID],
1673                           im->grid_dash_on, im->grid_dash_off);
1674         }
1675     }
1676
1677     /* paint major grid and labels*/
1678     for (value = pow((double)10, log10(im->minval) 
1679                           - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1680          value <= im->maxval;
1681          value *= yloglab[majoridx][0]){
1682         if (value < im->minval) continue;
1683         i=0;    
1684         while(yloglab[majoridx][++i] > 0){          
1685            Y0 = ytr(im,value * yloglab[majoridx][i]);    
1686            if (Y0 <= im->yorigin - im->ysize) break;
1687            gfx_new_dashed_line ( im->canvas,
1688                           X0-2,Y0,
1689                           X1+2,Y0,
1690                           MGRIDWIDTH, im->graph_col[GRC_MGRID],
1691                           im->grid_dash_on, im->grid_dash_off);
1692            
1693            sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1694            gfx_new_text ( im->canvas,
1695                           X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1696                           im->graph_col[GRC_FONT],
1697                           im->text_prop[TEXT_PROP_AXIS].font,
1698                           im->text_prop[TEXT_PROP_AXIS].size,
1699                           im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1700                           graph_label );
1701         } 
1702     }
1703         return 1;
1704 }
1705
1706
1707 void
1708 vertical_grid(
1709     image_desc_t   *im )
1710 {   
1711     int xlab_sel;               /* which sort of label and grid ? */
1712     time_t ti, tilab, timajor;
1713     long factor;
1714     char graph_label[100];
1715     double X0,Y0,Y1; /* points for filled graph and more*/
1716     struct tm tm;
1717
1718     /* the type of time grid is determined by finding
1719        the number of seconds per pixel in the graph */
1720     
1721     
1722     if(im->xlab_user.minsec == -1){
1723         factor=(im->end - im->start)/im->xsize;
1724         xlab_sel=0;
1725         while ( xlab[xlab_sel+1].minsec != -1 
1726                 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1727         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1728         im->xlab_user.gridst = xlab[xlab_sel].gridst;
1729         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1730         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1731         im->xlab_user.labtm = xlab[xlab_sel].labtm;
1732         im->xlab_user.labst = xlab[xlab_sel].labst;
1733         im->xlab_user.precis = xlab[xlab_sel].precis;
1734         im->xlab_user.stst = xlab[xlab_sel].stst;
1735     }
1736     
1737     /* y coords are the same for every line ... */
1738     Y0 = im->yorigin;
1739     Y1 = im->yorigin-im->ysize;
1740    
1741
1742     /* paint the minor grid */
1743     if (!(im->extra_flags & NOMINOR))
1744     {
1745         for(ti = find_first_time(im->start,
1746                                 im->xlab_user.gridtm,
1747                                 im->xlab_user.gridst),
1748             timajor = find_first_time(im->start,
1749                                 im->xlab_user.mgridtm,
1750                                 im->xlab_user.mgridst);
1751             ti < im->end; 
1752             ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1753             ){
1754             /* are we inside the graph ? */
1755             if (ti < im->start || ti > im->end) continue;
1756             while (timajor < ti) {
1757                 timajor = find_next_time(timajor,
1758                         im->xlab_user.mgridtm, im->xlab_user.mgridst);
1759             }
1760             if (ti == timajor) continue; /* skip as falls on major grid line */
1761            X0 = xtr(im,ti);       
1762            gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1763                im->graph_col[GRC_GRID],
1764                im->grid_dash_on, im->grid_dash_off);
1765            
1766         }
1767     }
1768
1769     /* paint the major grid */
1770     for(ti = find_first_time(im->start,
1771                             im->xlab_user.mgridtm,
1772                             im->xlab_user.mgridst);
1773         ti < im->end; 
1774         ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1775         ){
1776         /* are we inside the graph ? */
1777         if (ti < im->start || ti > im->end) continue;
1778        X0 = xtr(im,ti);
1779        gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1780            im->graph_col[GRC_MGRID],
1781            im->grid_dash_on, im->grid_dash_off);
1782        
1783     }
1784     /* paint the labels below the graph */
1785     for(ti = find_first_time(im->start,
1786                             im->xlab_user.labtm,
1787                             im->xlab_user.labst);
1788         ti <= im->end; 
1789         ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1790         ){
1791         tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1792         /* are we inside the graph ? */
1793         if (ti < im->start || ti > im->end) continue;
1794
1795 #if HAVE_STRFTIME
1796         localtime_r(&tilab, &tm);
1797         strftime(graph_label,99,im->xlab_user.stst, &tm);
1798 #else
1799 # error "your libc has no strftime I guess we'll abort the exercise here."
1800 #endif
1801        gfx_new_text ( im->canvas,
1802                       xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1803                       im->graph_col[GRC_FONT],
1804                       im->text_prop[TEXT_PROP_AXIS].font,
1805                       im->text_prop[TEXT_PROP_AXIS].size,
1806                       im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1807                       graph_label );
1808        
1809     }
1810
1811 }
1812
1813
1814 void 
1815 axis_paint(
1816    image_desc_t   *im
1817            )
1818 {   
1819     /* draw x and y axis */
1820     gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1821                       im->xorigin+im->xsize,im->yorigin-im->ysize,
1822                       GRIDWIDTH, im->graph_col[GRC_GRID]);
1823        
1824        gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1825                          im->xorigin+im->xsize,im->yorigin-im->ysize,
1826                          GRIDWIDTH, im->graph_col[GRC_GRID]);
1827    
1828        gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1829                          im->xorigin+im->xsize+4,im->yorigin,
1830                          MGRIDWIDTH, im->graph_col[GRC_GRID]);
1831    
1832        gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1833                          im->xorigin,im->yorigin-im->ysize-4,
1834                          MGRIDWIDTH, im->graph_col[GRC_GRID]);
1835    
1836     
1837     /* arrow for X axis direction */
1838     gfx_new_area ( im->canvas, 
1839                    im->xorigin+im->xsize+3,  im->yorigin-3,
1840                    im->xorigin+im->xsize+3,  im->yorigin+4,
1841                    im->xorigin+im->xsize+8,  im->yorigin+0.5, /* LINEOFFSET */
1842                    im->graph_col[GRC_ARROW]);
1843    
1844    
1845    
1846 }
1847
1848 void
1849 grid_paint(image_desc_t   *im)
1850 {   
1851     long i;
1852     int res=0;
1853     double X0,Y0; /* points for filled graph and more*/
1854     gfx_node_t *node;
1855
1856     /* draw 3d border */
1857     node = gfx_new_area (im->canvas, 0,im->yimg,
1858                                  2,im->yimg-2,
1859                                  2,2,im->graph_col[GRC_SHADEA]);
1860     gfx_add_point( node , im->ximg - 2, 2 );
1861     gfx_add_point( node , im->ximg, 0 );
1862     gfx_add_point( node , 0,0 );
1863 /*    gfx_add_point( node , 0,im->yimg ); */
1864    
1865     node =  gfx_new_area (im->canvas, 2,im->yimg-2,
1866                                   im->ximg-2,im->yimg-2,
1867                                   im->ximg - 2, 2,
1868                                  im->graph_col[GRC_SHADEB]);
1869     gfx_add_point( node ,   im->ximg,0);
1870     gfx_add_point( node ,   im->ximg,im->yimg);
1871     gfx_add_point( node ,   0,im->yimg);
1872 /*    gfx_add_point( node , 0,im->yimg ); */
1873    
1874    
1875     if (im->draw_x_grid == 1 )
1876       vertical_grid(im);
1877     
1878     if (im->draw_y_grid == 1){
1879         if(im->logarithmic){
1880                 res = horizontal_log_grid(im);
1881         } else {
1882                 res = draw_horizontal_grid(im);
1883         }
1884
1885         /* dont draw horizontal grid if there is no min and max val */
1886         if (! res ) {
1887           char *nodata = "No Data found";
1888            gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1889                         im->graph_col[GRC_FONT],
1890                         im->text_prop[TEXT_PROP_AXIS].font,
1891                         im->text_prop[TEXT_PROP_AXIS].size,
1892                         im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1893                         nodata );          
1894         }
1895     }
1896
1897     /* yaxis description */
1898 /*      if (im->canvas->imgformat != IF_PNG) {*/
1899         if (1) {
1900             gfx_new_text( im->canvas,
1901                           7, (im->yorigin - im->ysize/2),
1902                           im->graph_col[GRC_FONT],
1903                           im->text_prop[TEXT_PROP_AXIS].font,
1904                           im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 
1905                           RRDGRAPH_YLEGEND_ANGLE,
1906                           GFX_H_LEFT, GFX_V_CENTER,
1907                           im->ylegend);
1908         } else {
1909             /* horrible hack until we can actually print vertically */
1910             {
1911                 int n;
1912                 char s[2];
1913                 for (n=0;n< (int)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((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2857                     rrd_set_error("unknown keyword %s",scan_gtm);
2858                     return;
2859                 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2860                     rrd_set_error("unknown keyword %s",scan_mtm);
2861                     return;
2862                 } else if ((int)(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((int)(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== (int)strlen(str)) { /* matched */
3171         ;
3172     } else {
3173         n=0;
3174         sscanf(str,"%29[A-Z]%n",func,&n);
3175         if (n== (int)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 }