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