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