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