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