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