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