put all the architecture specific stuff in separate subdirectories ... one for netwar...
[rrdtool.git] / src / rrd_graph.c
1 /****************************************************************************
2  * RRDtool 1.2.15  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         /* remove exess space */
1450         while (prt_fctn=='g' && 
1451                leg_cc > 0 && 
1452                im->gdes[i].legend[leg_cc-1]==' '){
1453            leg_cc--;
1454            im->gdes[i].legend[leg_cc]='\0';
1455         }
1456         if (leg_cc != 0 ){
1457            legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1458            
1459            if (fill > 0){ 
1460                /* no interleg space if string ends in \g */
1461                fill += legspace[i];
1462             }
1463            fill += gfx_get_text_width(im->canvas, fill+border,
1464                                       im->text_prop[TEXT_PROP_LEGEND].font,
1465                                       im->text_prop[TEXT_PROP_LEGEND].size,
1466                                       im->tabwidth,
1467                                       im->gdes[i].legend, 0);
1468             leg_c++;
1469         } else {
1470            legspace[i]=0;
1471         }
1472         /* who said there was a special tag ... ?*/
1473         if (prt_fctn=='g') {    
1474            prt_fctn = '\0';
1475         }
1476         if (prt_fctn == '\0') {
1477             if (i == im->gdes_c -1 ) prt_fctn ='l';
1478             
1479             /* is it time to place the legends ? */
1480             if (fill > im->ximg - 2*border){
1481                 if (leg_c > 1) {
1482                     /* go back one */
1483                     i--; 
1484                     fill = fill_last;
1485                     leg_c--;
1486                     prt_fctn = 'j';
1487                 } else {
1488                     prt_fctn = 'l';
1489                 }
1490                 
1491             }
1492         }
1493
1494
1495         if (prt_fctn != '\0'){  
1496             leg_x = border;
1497             if (leg_c >= 2 && prt_fctn == 'j') {
1498                 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1499             } else {
1500                 glue = 0;
1501             }
1502             if (prt_fctn =='c') leg_x =  (im->ximg - fill) / 2.0;
1503             if (prt_fctn =='r') leg_x =  im->ximg - fill - border;
1504
1505             for(ii=mark;ii<=i;ii++){
1506                 if(im->gdes[ii].legend[0]=='\0')
1507                     continue; /* skip empty legends */
1508                 im->gdes[ii].leg_x = leg_x;
1509                 im->gdes[ii].leg_y = leg_y;
1510                 leg_x += 
1511                  gfx_get_text_width(im->canvas, leg_x,
1512                                       im->text_prop[TEXT_PROP_LEGEND].font,
1513                                       im->text_prop[TEXT_PROP_LEGEND].size,
1514                                       im->tabwidth,
1515                                       im->gdes[ii].legend, 0) 
1516                    + legspace[ii]
1517                    + glue;
1518             }                   
1519             leg_y_prev = leg_y;
1520             /* only add y space if there was text on the line */
1521             if (leg_x > border || prt_fctn == 's')            
1522                leg_y += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1523             if (prt_fctn == 's')
1524                leg_y -=  im->text_prop[TEXT_PROP_LEGEND].size;     
1525             fill = 0;
1526             leg_c = 0;
1527             mark = ii;
1528         }          
1529     }
1530     im->yimg = leg_y_prev;
1531     /* if we did place some legends we have to add vertical space */
1532     if (leg_y != im->yimg){
1533         im->yimg += im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1534     }
1535     free(legspace);
1536   }
1537   return 0;
1538 }
1539
1540 /* create a grid on the graph. it determines what to do
1541    from the values of xsize, start and end */
1542
1543 /* the xaxis labels are determined from the number of seconds per pixel
1544    in the requested graph */
1545
1546
1547
1548 int
1549 calc_horizontal_grid(image_desc_t   *im)
1550 {
1551     double   range;
1552     double   scaledrange;
1553     int      pixel,i;
1554     int      gridind=0;
1555     int      decimals, fractionals;
1556
1557     im->ygrid_scale.labfact=2;
1558     range =  im->maxval - im->minval;
1559     scaledrange = range / im->magfact;
1560
1561         /* does the scale of this graph make it impossible to put lines
1562            on it? If so, give up. */
1563         if (isnan(scaledrange)) {
1564                 return 0;
1565         }
1566
1567     /* find grid spaceing */
1568     pixel=1;
1569     if(isnan(im->ygridstep)){
1570         if(im->extra_flags & ALTYGRID) {
1571             /* find the value with max number of digits. Get number of digits */
1572             decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))*im->viewfactor/im->magfact));
1573             if(decimals <= 0) /* everything is small. make place for zero */
1574                 decimals = 1;
1575             
1576             im->ygrid_scale.gridstep = pow((double)10, floor(log10(range*im->viewfactor/im->magfact)))/im->viewfactor*im->magfact;
1577             
1578             if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1579                 im->ygrid_scale.gridstep = 0.1;
1580             /* should have at least 5 lines but no more then 15 */
1581             if(range/im->ygrid_scale.gridstep < 5)
1582                 im->ygrid_scale.gridstep /= 10;
1583             if(range/im->ygrid_scale.gridstep > 15)
1584                 im->ygrid_scale.gridstep *= 10;
1585             if(range/im->ygrid_scale.gridstep > 5) {
1586                 im->ygrid_scale.labfact = 1;
1587                 if(range/im->ygrid_scale.gridstep > 8)
1588                     im->ygrid_scale.labfact = 2;
1589             }
1590             else {
1591                 im->ygrid_scale.gridstep /= 5;
1592                 im->ygrid_scale.labfact = 5;
1593             }
1594             fractionals = floor(log10(im->ygrid_scale.gridstep*(double)im->ygrid_scale.labfact*im->viewfactor/im->magfact));
1595             if(fractionals < 0) { /* small amplitude. */
1596                 int len = decimals - fractionals + 1;
1597                 if (im->unitslength < len+2) im->unitslength = len+2;
1598                 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len, -fractionals,(im->symbol != ' ' ? " %c" : ""));
1599             } else {
1600                 int len = decimals + 1;
1601                 if (im->unitslength < len+2) im->unitslength = len+2;
1602                 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len, ( im->symbol != ' ' ? " %c" : "" ));
1603             }
1604         }
1605         else {
1606             for(i=0;ylab[i].grid > 0;i++){
1607                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1608                 gridind = i;
1609                 if (pixel > 7)
1610                     break;
1611             }
1612             
1613             for(i=0; i<4;i++) {
1614                if (pixel * ylab[gridind].lfac[i] >=  2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1615                   im->ygrid_scale.labfact =  ylab[gridind].lfac[i];
1616                   break;
1617                }
1618             } 
1619             
1620             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1621         }
1622     } else {
1623         im->ygrid_scale.gridstep = im->ygridstep;
1624         im->ygrid_scale.labfact = im->ylabfact;
1625     }
1626     return 1;
1627 }
1628
1629 int draw_horizontal_grid(image_desc_t *im)
1630 {
1631     int      i;
1632     double   scaledstep;
1633     char     graph_label[100];
1634     int      nlabels=0;
1635     double X0=im->xorigin;
1636     double X1=im->xorigin+im->xsize;
1637    
1638     int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1639     int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1640     double MaxY;
1641     scaledstep = im->ygrid_scale.gridstep/(double)im->magfact*(double)im->viewfactor;
1642     MaxY = scaledstep*(double)egrid;
1643     for (i = sgrid; i <= egrid; i++){
1644        double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1645        double YN=ytr(im,im->ygrid_scale.gridstep*(i+1));
1646        if ( Y0 >= im->yorigin-im->ysize
1647                  && Y0 <= im->yorigin){       
1648             /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1649                with the chosen settings. Add a label if required by settings, or if
1650                there is only one label so far and the next grid line is out of bounds. */
1651             if(i % im->ygrid_scale.labfact == 0 || ( nlabels==1 && (YN < im->yorigin-im->ysize || YN > im->yorigin) )){         
1652                 if (im->symbol == ' ') {
1653                     if(im->extra_flags & ALTYGRID) {
1654                         sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i);
1655                     } else {
1656                         if(MaxY < 10) {
1657                            sprintf(graph_label,"%4.1f",scaledstep*(double)i);
1658                         } else {
1659                            sprintf(graph_label,"%4.0f",scaledstep*(double)i);
1660                         }
1661                     }
1662                 }else {
1663                     char sisym = ( i == 0  ? ' ' : im->symbol);
1664                     if(im->extra_flags & ALTYGRID) {
1665                         sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*(double)i,sisym);
1666                     } else {
1667                         if(MaxY < 10){
1668                           sprintf(graph_label,"%4.1f %c",scaledstep*(double)i, sisym);
1669                         } else {
1670                           sprintf(graph_label,"%4.0f %c",scaledstep*(double)i, sisym);
1671                         }
1672                     }
1673                 }
1674                 nlabels++;
1675
1676                gfx_new_text ( im->canvas,
1677                               X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1678                               im->graph_col[GRC_FONT],
1679                               im->text_prop[TEXT_PROP_AXIS].font,
1680                               im->text_prop[TEXT_PROP_AXIS].size,
1681                               im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1682                               graph_label );
1683                gfx_new_dashed_line ( im->canvas,
1684                               X0-2,Y0,
1685                               X1+2,Y0,
1686                               MGRIDWIDTH, im->graph_col[GRC_MGRID],
1687                               im->grid_dash_on, im->grid_dash_off);            
1688                
1689             } else if (!(im->extra_flags & NOMINOR)) {          
1690                gfx_new_dashed_line ( im->canvas,
1691                               X0-1,Y0,
1692                               X1+1,Y0,
1693                               GRIDWIDTH, im->graph_col[GRC_GRID],
1694                               im->grid_dash_on, im->grid_dash_off);            
1695                
1696             }       
1697         }       
1698     } 
1699     return 1;
1700 }
1701
1702 /* this is frexp for base 10 */
1703 double frexp10(double, double *);
1704 double frexp10(double x, double *e) {
1705     double mnt;
1706     int iexp;
1707
1708     iexp = floor(log(fabs(x)) / log(10));
1709     mnt = x / pow(10.0, iexp);
1710     if(mnt >= 10.0) {
1711         iexp++;
1712         mnt = x / pow(10.0, iexp);
1713     }
1714     *e = iexp;
1715     return mnt;
1716 }
1717
1718 static int AlmostEqual2sComplement (float A, float B, int maxUlps)
1719 {
1720
1721     int aInt = *(int*)&A;
1722     int bInt = *(int*)&B;
1723     int intDiff;
1724     /* Make sure maxUlps is non-negative and small enough that the
1725        default NAN won't compare as equal to anything.  */
1726
1727     /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1728
1729     /* Make aInt lexicographically ordered as a twos-complement int */
1730
1731     if (aInt < 0)
1732         aInt = 0x80000000l - aInt;
1733
1734     /* Make bInt lexicographically ordered as a twos-complement int */
1735
1736     if (bInt < 0)
1737         bInt = 0x80000000l - bInt;
1738
1739     intDiff = abs(aInt - bInt);
1740
1741     if (intDiff <= maxUlps)
1742         return 1;
1743
1744     return 0;
1745 }
1746
1747 /* logaritmic horizontal grid */
1748 int
1749 horizontal_log_grid(image_desc_t   *im)   
1750 {
1751     double yloglab[][10] = {
1752         {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1753         {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1754         {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1755         {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1756         {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1757         {0,0,0,0,0, 0,0,0,0,0} /* last line */ };
1758
1759     int i, j, val_exp, min_exp;
1760     double nex;         /* number of decades in data */
1761     double logscale;    /* scale in logarithmic space */
1762     int exfrac = 1;     /* decade spacing */
1763     int mid = -1;       /* row in yloglab for major grid */
1764     double mspac;       /* smallest major grid spacing (pixels) */
1765     int flab;           /* first value in yloglab to use */
1766     double value, tmp, pre_value;
1767     double X0,X1,Y0;   
1768     char graph_label[100];
1769
1770     nex = log10(im->maxval / im->minval);
1771     logscale = im->ysize / nex;
1772
1773     /* major spacing for data with high dynamic range */
1774     while(logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1775         if(exfrac == 1) exfrac = 3;
1776         else exfrac += 3;
1777     }
1778
1779     /* major spacing for less dynamic data */
1780     do {
1781         /* search best row in yloglab */
1782         mid++;
1783         for(i = 0; yloglab[mid][i + 1] < 10.0; i++);
1784         mspac = logscale * log10(10.0 / yloglab[mid][i]);
1785     } while(mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
1786     if(mid) mid--;
1787
1788     /* find first value in yloglab */
1789     for(flab = 0; yloglab[mid][flab] < 10 && frexp10(im->minval, &tmp) > yloglab[mid][flab] ; flab++);
1790     if(yloglab[mid][flab] == 10.0) {
1791         tmp += 1.0;
1792         flab = 0;
1793     }
1794     val_exp = tmp;
1795     if(val_exp % exfrac) val_exp += abs(-val_exp % exfrac);
1796
1797     X0=im->xorigin;
1798     X1=im->xorigin+im->xsize;
1799
1800     /* draw grid */
1801     pre_value = DNAN;
1802     while(1) {       
1803
1804         value = yloglab[mid][flab] * pow(10.0, val_exp);
1805         if (  AlmostEqual2sComplement(value,pre_value,4) ) break; /* it seems we are not converging */
1806
1807         pre_value = value;
1808
1809         Y0 = ytr(im, value);
1810         if(Y0 <= im->yorigin - im->ysize) break;
1811
1812         /* major grid line */
1813         gfx_new_dashed_line ( im->canvas,
1814             X0-2,Y0,
1815             X1+2,Y0,
1816             MGRIDWIDTH, im->graph_col[GRC_MGRID],
1817             im->grid_dash_on, im->grid_dash_off);
1818
1819         /* label */
1820         if (im->extra_flags & FORCE_UNITS_SI) {
1821             int scale;
1822             double pvalue;
1823             char symbol;
1824
1825             scale = floor(val_exp / 3.0);
1826             if( value >= 1.0 ) pvalue = pow(10.0, val_exp % 3);
1827             else pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
1828             pvalue *= yloglab[mid][flab];
1829
1830             if ( ((scale+si_symbcenter) < (int)sizeof(si_symbol)) &&
1831                 ((scale+si_symbcenter) >= 0) )
1832                 symbol = si_symbol[scale+si_symbcenter];
1833             else
1834                 symbol = '?';
1835
1836                 sprintf(graph_label,"%3.0f %c", pvalue, symbol);
1837         } else
1838             sprintf(graph_label,"%3.0e", value);
1839         gfx_new_text ( im->canvas,
1840             X0-im->text_prop[TEXT_PROP_AXIS].size, Y0,
1841             im->graph_col[GRC_FONT],
1842             im->text_prop[TEXT_PROP_AXIS].font,
1843             im->text_prop[TEXT_PROP_AXIS].size,
1844             im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1845             graph_label );
1846
1847         /* minor grid */
1848         if(mid < 4 && exfrac == 1) {
1849             /* find first and last minor line behind current major line
1850              * i is the first line and j tha last */
1851             if(flab == 0) {
1852                 min_exp = val_exp - 1;
1853                 for(i = 1; yloglab[mid][i] < 10.0; i++);
1854                 i = yloglab[mid][i - 1] + 1;
1855                 j = 10;
1856             }
1857             else {
1858                 min_exp = val_exp;
1859                 i = yloglab[mid][flab - 1] + 1;
1860                 j = yloglab[mid][flab];
1861             }
1862
1863             /* draw minor lines below current major line */
1864             for(; i < j; i++) {
1865
1866                 value = i * pow(10.0, min_exp);
1867                 if(value < im->minval) continue;
1868
1869                 Y0 = ytr(im, value);
1870                 if(Y0 <= im->yorigin - im->ysize) break;
1871
1872                 /* draw lines */
1873                 gfx_new_dashed_line ( im->canvas,
1874                     X0-1,Y0,
1875                     X1+1,Y0,
1876                     GRIDWIDTH, im->graph_col[GRC_GRID],
1877                     im->grid_dash_on, im->grid_dash_off);
1878             }
1879         }
1880         else if(exfrac > 1) {
1881             for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1882                 value = pow(10.0, i);
1883                 if(value < im->minval) continue;
1884
1885                 Y0 = ytr(im, value);
1886                 if(Y0 <= im->yorigin - im->ysize) break;
1887
1888                 /* draw lines */
1889                 gfx_new_dashed_line ( im->canvas,
1890                     X0-1,Y0,
1891                     X1+1,Y0,
1892                     GRIDWIDTH, im->graph_col[GRC_GRID],
1893                     im->grid_dash_on, im->grid_dash_off);
1894             }
1895         }
1896
1897         /* next decade */
1898         if(yloglab[mid][++flab] == 10.0) {
1899             flab = 0;
1900             val_exp += exfrac;
1901         }
1902     }
1903
1904     /* draw minor lines after highest major line */
1905     if(mid < 4 && exfrac == 1) {
1906         /* find first and last minor line below current major line
1907          * i is the first line and j tha last */
1908         if(flab == 0) {
1909             min_exp = val_exp - 1;
1910             for(i = 1; yloglab[mid][i] < 10.0; i++);
1911             i = yloglab[mid][i - 1] + 1;
1912             j = 10;
1913         }
1914         else {
1915             min_exp = val_exp;
1916             i = yloglab[mid][flab - 1] + 1;
1917             j = yloglab[mid][flab];
1918         }
1919
1920         /* draw minor lines below current major line */
1921         for(; i < j; i++) {
1922
1923             value = i * pow(10.0, min_exp);
1924             if(value < im->minval) continue;
1925
1926             Y0 = ytr(im, value);
1927             if(Y0 <= im->yorigin - im->ysize) break;
1928
1929             /* draw lines */
1930             gfx_new_dashed_line ( im->canvas,
1931                 X0-1,Y0,
1932                 X1+1,Y0,
1933                 GRIDWIDTH, im->graph_col[GRC_GRID],
1934                 im->grid_dash_on, im->grid_dash_off);
1935         }
1936     }
1937     /* fancy minor gridlines */
1938     else if(exfrac > 1) {
1939         for(i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
1940             value = pow(10.0, i);
1941             if(value < im->minval) continue;
1942
1943             Y0 = ytr(im, value);
1944             if(Y0 <= im->yorigin - im->ysize) break;
1945
1946             /* draw lines */
1947             gfx_new_dashed_line ( im->canvas,
1948                 X0-1,Y0,
1949                 X1+1,Y0,
1950                 GRIDWIDTH, im->graph_col[GRC_GRID],
1951                 im->grid_dash_on, im->grid_dash_off);
1952         }
1953     }
1954
1955     return 1;
1956 }
1957
1958
1959 void
1960 vertical_grid(
1961     image_desc_t   *im )
1962 {   
1963     int xlab_sel;               /* which sort of label and grid ? */
1964     time_t ti, tilab, timajor;
1965     long factor;
1966     char graph_label[100];
1967     double X0,Y0,Y1; /* points for filled graph and more*/
1968     struct tm tm;
1969
1970     /* the type of time grid is determined by finding
1971        the number of seconds per pixel in the graph */
1972     
1973     
1974     if(im->xlab_user.minsec == -1){
1975         factor=(im->end - im->start)/im->xsize;
1976         xlab_sel=0;
1977         while ( xlab[xlab_sel+1].minsec != -1 
1978                 && xlab[xlab_sel+1].minsec <= factor) { xlab_sel++; }   /* pick the last one */
1979         while ( xlab[xlab_sel-1].minsec == xlab[xlab_sel].minsec
1980                 && xlab[xlab_sel].length > (im->end - im->start)) { xlab_sel--; }       /* go back to the smallest size */
1981         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1982         im->xlab_user.gridst = xlab[xlab_sel].gridst;
1983         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1984         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1985         im->xlab_user.labtm = xlab[xlab_sel].labtm;
1986         im->xlab_user.labst = xlab[xlab_sel].labst;
1987         im->xlab_user.precis = xlab[xlab_sel].precis;
1988         im->xlab_user.stst = xlab[xlab_sel].stst;
1989     }
1990     
1991     /* y coords are the same for every line ... */
1992     Y0 = im->yorigin;
1993     Y1 = im->yorigin-im->ysize;
1994    
1995
1996     /* paint the minor grid */
1997     if (!(im->extra_flags & NOMINOR))
1998     {
1999         for(ti = find_first_time(im->start,
2000                                 im->xlab_user.gridtm,
2001                                 im->xlab_user.gridst),
2002             timajor = find_first_time(im->start,
2003                                 im->xlab_user.mgridtm,
2004                                 im->xlab_user.mgridst);
2005             ti < im->end; 
2006             ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
2007             ){
2008             /* are we inside the graph ? */
2009             if (ti < im->start || ti > im->end) continue;
2010             while (timajor < ti) {
2011                 timajor = find_next_time(timajor,
2012                         im->xlab_user.mgridtm, im->xlab_user.mgridst);
2013             }
2014             if (ti == timajor) continue; /* skip as falls on major grid line */
2015            X0 = xtr(im,ti);       
2016            gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
2017                im->graph_col[GRC_GRID],
2018                im->grid_dash_on, im->grid_dash_off);
2019            
2020         }
2021     }
2022
2023     /* paint the major grid */
2024     for(ti = find_first_time(im->start,
2025                             im->xlab_user.mgridtm,
2026                             im->xlab_user.mgridst);
2027         ti < im->end; 
2028         ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
2029         ){
2030         /* are we inside the graph ? */
2031         if (ti < im->start || ti > im->end) continue;
2032        X0 = xtr(im,ti);
2033        gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
2034            im->graph_col[GRC_MGRID],
2035            im->grid_dash_on, im->grid_dash_off);
2036        
2037     }
2038     /* paint the labels below the graph */
2039     for(ti = find_first_time(im->start - im->xlab_user.precis/2,
2040                             im->xlab_user.labtm,
2041                             im->xlab_user.labst);
2042         ti <= im->end - im->xlab_user.precis/2; 
2043         ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
2044         ){
2045         tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
2046         /* are we inside the graph ? */
2047         if (tilab < im->start || tilab > im->end) continue;
2048
2049 #if HAVE_STRFTIME
2050         localtime_r(&tilab, &tm);
2051         strftime(graph_label,99,im->xlab_user.stst, &tm);
2052 #else
2053 # error "your libc has no strftime I guess we'll abort the exercise here."
2054 #endif
2055        gfx_new_text ( im->canvas,
2056                       xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size*1.4+5,
2057                       im->graph_col[GRC_FONT],
2058                       im->text_prop[TEXT_PROP_AXIS].font,
2059                       im->text_prop[TEXT_PROP_AXIS].size,
2060                       im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_BOTTOM,
2061                       graph_label );
2062        
2063     }
2064
2065 }
2066
2067
2068 void 
2069 axis_paint(
2070    image_desc_t   *im
2071            )
2072 {   
2073     /* draw x and y axis */
2074     /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2075                       im->xorigin+im->xsize,im->yorigin-im->ysize,
2076                       GRIDWIDTH, im->graph_col[GRC_AXIS]);
2077        
2078        gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2079                          im->xorigin+im->xsize,im->yorigin-im->ysize,
2080                          GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2081    
2082        gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
2083                          im->xorigin+im->xsize+4,im->yorigin,
2084                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2085    
2086        gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
2087                          im->xorigin,im->yorigin-im->ysize-4,
2088                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2089    
2090     
2091     /* arrow for X and Y axis direction */
2092     gfx_new_area ( im->canvas, 
2093                    im->xorigin+im->xsize+2,  im->yorigin-2,
2094                    im->xorigin+im->xsize+2,  im->yorigin+3,
2095                    im->xorigin+im->xsize+7,  im->yorigin+0.5, /* LINEOFFSET */
2096                    im->graph_col[GRC_ARROW]);
2097
2098     gfx_new_area ( im->canvas, 
2099                    im->xorigin-2,  im->yorigin-im->ysize-2,
2100                    im->xorigin+3,  im->yorigin-im->ysize-2,
2101                    im->xorigin+0.5,    im->yorigin-im->ysize-7, /* LINEOFFSET */
2102                    im->graph_col[GRC_ARROW]);
2103
2104 }
2105
2106 void
2107 grid_paint(image_desc_t   *im)
2108 {   
2109     long i;
2110     int res=0;
2111     double X0,Y0; /* points for filled graph and more*/
2112     gfx_node_t *node;
2113
2114     /* draw 3d border */
2115     node = gfx_new_area (im->canvas, 0,im->yimg,
2116                                  2,im->yimg-2,
2117                                  2,2,im->graph_col[GRC_SHADEA]);
2118     gfx_add_point( node , im->ximg - 2, 2 );
2119     gfx_add_point( node , im->ximg, 0 );
2120     gfx_add_point( node , 0,0 );
2121 /*    gfx_add_point( node , 0,im->yimg ); */
2122    
2123     node =  gfx_new_area (im->canvas, 2,im->yimg-2,
2124                                   im->ximg-2,im->yimg-2,
2125                                   im->ximg - 2, 2,
2126                                  im->graph_col[GRC_SHADEB]);
2127     gfx_add_point( node ,   im->ximg,0);
2128     gfx_add_point( node ,   im->ximg,im->yimg);
2129     gfx_add_point( node ,   0,im->yimg);
2130 /*    gfx_add_point( node , 0,im->yimg ); */
2131    
2132    
2133     if (im->draw_x_grid == 1 )
2134       vertical_grid(im);
2135     
2136     if (im->draw_y_grid == 1){
2137         if(im->logarithmic){
2138                 res = horizontal_log_grid(im);
2139         } else {
2140                 res = draw_horizontal_grid(im);
2141         }
2142         
2143         /* dont draw horizontal grid if there is no min and max val */
2144         if (! res ) {
2145           char *nodata = "No Data found";
2146            gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
2147                         im->graph_col[GRC_FONT],
2148                         im->text_prop[TEXT_PROP_AXIS].font,
2149                         im->text_prop[TEXT_PROP_AXIS].size,
2150                         im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
2151                         nodata );          
2152         }
2153     }
2154
2155     /* yaxis unit description */
2156     gfx_new_text( im->canvas,
2157                   10, (im->yorigin - im->ysize/2),
2158                   im->graph_col[GRC_FONT],
2159                   im->text_prop[TEXT_PROP_UNIT].font,
2160                   im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth, 
2161                   RRDGRAPH_YLEGEND_ANGLE,
2162                   GFX_H_LEFT, GFX_V_CENTER,
2163                   im->ylegend);
2164
2165     /* graph title */
2166     gfx_new_text( im->canvas,
2167                   im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size*1.3+4,
2168                   im->graph_col[GRC_FONT],
2169                   im->text_prop[TEXT_PROP_TITLE].font,
2170                   im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2171                   GFX_H_CENTER, GFX_V_CENTER,
2172                   im->title);
2173     /* rrdtool 'logo' */
2174     gfx_new_text( im->canvas,
2175                   im->ximg-7, 7,
2176                   ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2177                   im->text_prop[TEXT_PROP_AXIS].font,
2178                   5.5, im->tabwidth, 270,
2179                   GFX_H_RIGHT, GFX_V_TOP,
2180                   "RRDTOOL / TOBI OETIKER");
2181
2182     /* graph watermark */
2183     if(im->watermark[0] != '\0') {
2184         gfx_new_text( im->canvas,
2185                   im->ximg/2, im->yimg-6,
2186                   ( im->graph_col[GRC_FONT] & 0xffffff00 ) | 0x00000044,
2187                   im->text_prop[TEXT_PROP_AXIS].font,
2188                   5.5, im->tabwidth, 0,
2189                   GFX_H_CENTER, GFX_V_BOTTOM,
2190                   im->watermark);
2191     }
2192     
2193     /* graph labels */
2194     if( !(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH) ) {
2195             for(i=0;i<im->gdes_c;i++){
2196                     if(im->gdes[i].legend[0] =='\0')
2197                             continue;
2198                     
2199                     /* im->gdes[i].leg_y is the bottom of the legend */
2200                     X0 = im->gdes[i].leg_x;
2201                     Y0 = im->gdes[i].leg_y;
2202                     gfx_new_text ( im->canvas, X0, Y0,
2203                                    im->graph_col[GRC_FONT],
2204                                    im->text_prop[TEXT_PROP_LEGEND].font,
2205                                    im->text_prop[TEXT_PROP_LEGEND].size,
2206                                    im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2207                                    im->gdes[i].legend );
2208                     /* The legend for GRAPH items starts with "M " to have
2209                        enough space for the box */
2210                     if (           im->gdes[i].gf != GF_PRINT &&
2211                                    im->gdes[i].gf != GF_GPRINT &&
2212                                    im->gdes[i].gf != GF_COMMENT) {
2213                             int boxH, boxV;
2214                             
2215                             boxH = gfx_get_text_width(im->canvas, 0,
2216                                                       im->text_prop[TEXT_PROP_LEGEND].font,
2217                                                       im->text_prop[TEXT_PROP_LEGEND].size,
2218                                                       im->tabwidth,"o", 0) * 1.2;
2219                             boxV = boxH*1.1;
2220                             
2221                             /* make sure transparent colors show up the same way as in the graph */
2222                              node = gfx_new_area(im->canvas,
2223                                                 X0,Y0-boxV,
2224                                                 X0,Y0,
2225                                                 X0+boxH,Y0,
2226                                                 im->graph_col[GRC_BACK]);
2227                             gfx_add_point ( node, X0+boxH, Y0-boxV );
2228
2229                             node = gfx_new_area(im->canvas,
2230                                                 X0,Y0-boxV,
2231                                                 X0,Y0,
2232                                                 X0+boxH,Y0,
2233                                                 im->gdes[i].col);
2234                             gfx_add_point ( node, X0+boxH, Y0-boxV );
2235                             node = gfx_new_line(im->canvas,
2236                                                 X0,Y0-boxV,
2237                                                 X0,Y0,
2238                                                 1.0,im->graph_col[GRC_FRAME]);
2239                             gfx_add_point(node,X0+boxH,Y0);
2240                             gfx_add_point(node,X0+boxH,Y0-boxV);
2241                             gfx_close_path(node);
2242                     }
2243             }
2244     }
2245 }
2246
2247
2248 /*****************************************************
2249  * lazy check make sure we rely need to create this graph
2250  *****************************************************/
2251
2252 int lazy_check(image_desc_t *im){
2253     FILE *fd = NULL;
2254         int size = 1;
2255     struct stat  imgstat;
2256     
2257     if (im->lazy == 0) return 0; /* no lazy option */
2258     if (stat(im->graphfile,&imgstat) != 0) 
2259       return 0; /* can't stat */
2260     /* one pixel in the existing graph is more then what we would
2261        change here ... */
2262     if (time(NULL) - imgstat.st_mtime > 
2263         (im->end - im->start) / im->xsize) 
2264       return 0;
2265     if ((fd = fopen(im->graphfile,"rb")) == NULL) 
2266       return 0; /* the file does not exist */
2267     switch (im->canvas->imgformat) {
2268     case IF_PNG:
2269            size = PngSize(fd,&(im->ximg),&(im->yimg));
2270            break;
2271     default:
2272            size = 1;
2273     }
2274     fclose(fd);
2275     return size;
2276 }
2277
2278 #ifdef WITH_PIECHART
2279 void
2280 pie_part(image_desc_t *im, gfx_color_t color,
2281             double PieCenterX, double PieCenterY, double Radius,
2282             double startangle, double endangle)
2283 {
2284     gfx_node_t *node;
2285     double angle;
2286     double step=M_PI/50; /* Number of iterations for the circle;
2287                          ** 10 is definitely too low, more than
2288                          ** 50 seems to be overkill
2289                          */
2290
2291     /* Strange but true: we have to work clockwise or else
2292     ** anti aliasing nor transparency don't work.
2293     **
2294     ** This test is here to make sure we do it right, also
2295     ** this makes the for...next loop more easy to implement.
2296     ** The return will occur if the user enters a negative number
2297     ** (which shouldn't be done according to the specs) or if the
2298     ** programmers do something wrong (which, as we all know, never
2299     ** happens anyway :)
2300     */
2301     if (endangle<startangle) return;
2302
2303     /* Hidden feature: Radius decreases each full circle */
2304     angle=startangle;
2305     while (angle>=2*M_PI) {
2306         angle  -= 2*M_PI;
2307         Radius *= 0.8;
2308     }
2309
2310     node=gfx_new_area(im->canvas,
2311                 PieCenterX+sin(startangle)*Radius,
2312                 PieCenterY-cos(startangle)*Radius,
2313                 PieCenterX,
2314                 PieCenterY,
2315                 PieCenterX+sin(endangle)*Radius,
2316                 PieCenterY-cos(endangle)*Radius,
2317                 color);
2318     for (angle=endangle;angle-startangle>=step;angle-=step) {
2319         gfx_add_point(node,
2320                 PieCenterX+sin(angle)*Radius,
2321                 PieCenterY-cos(angle)*Radius );
2322     }
2323 }
2324
2325 #endif
2326
2327 int
2328 graph_size_location(image_desc_t *im, int elements
2329
2330 #ifdef WITH_PIECHART
2331 , int piechart
2332 #endif
2333
2334  )
2335 {
2336     /* The actual size of the image to draw is determined from
2337     ** several sources.  The size given on the command line is
2338     ** the graph area but we need more as we have to draw labels
2339     ** and other things outside the graph area
2340     */
2341
2342     /* +-+-------------------------------------------+
2343     ** |l|.................title.....................|
2344     ** |e+--+-------------------------------+--------+
2345     ** |b| b|                               |        |
2346     ** |a| a|                               |  pie   |
2347     ** |l| l|          main graph area      | chart  |
2348     ** |.| .|                               |  area  |
2349     ** |t| y|                               |        |
2350     ** |r+--+-------------------------------+--------+
2351     ** |e|  | x-axis labels                 |        |
2352     ** |v+--+-------------------------------+--------+
2353     ** | |..............legends......................|
2354     ** +-+-------------------------------------------+
2355     ** |                 watermark                   |
2356     ** +---------------------------------------------+
2357     */
2358     int Xvertical=0,    
2359                         Ytitle   =0,
2360         Xylabel  =0,    
2361         Xmain    =0,    Ymain    =0,
2362 #ifdef WITH_PIECHART
2363         Xpie     =0,    Ypie     =0,
2364 #endif
2365                         Yxlabel  =0,
2366 #if 0
2367         Xlegend  =0,    Ylegend  =0,
2368 #endif
2369         Xspacing =15,  Yspacing =15,
2370        
2371                       Ywatermark =4;
2372
2373     if (im->extra_flags & ONLY_GRAPH) {
2374         im->xorigin =0;
2375         im->ximg = im->xsize;
2376         im->yimg = im->ysize;
2377         im->yorigin = im->ysize;
2378         ytr(im,DNAN); 
2379         return 0;
2380     }
2381
2382     if (im->ylegend[0] != '\0' ) {
2383            Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2384     }
2385
2386
2387     if (im->title[0] != '\0') {
2388         /* The title is placed "inbetween" two text lines so it
2389         ** automatically has some vertical spacing.  The horizontal
2390         ** spacing is added here, on each side.
2391         */
2392         /* don't care for the with of the title
2393                 Xtitle = gfx_get_text_width(im->canvas, 0,
2394                 im->text_prop[TEXT_PROP_TITLE].font,
2395                 im->text_prop[TEXT_PROP_TITLE].size,
2396                 im->tabwidth,
2397                 im->title, 0) + 2*Xspacing; */
2398         Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2399     }
2400
2401     if (elements) {
2402         Xmain=im->xsize;
2403         Ymain=im->ysize;
2404         if (im->draw_x_grid) {
2405             Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2406         }
2407         if (im->draw_y_grid) {
2408             Xylabel=gfx_get_text_width(im->canvas, 0,
2409                         im->text_prop[TEXT_PROP_AXIS].font,
2410                         im->text_prop[TEXT_PROP_AXIS].size,
2411                         im->tabwidth,
2412                         "0", 0) * im->unitslength;
2413         }
2414     }
2415
2416 #ifdef WITH_PIECHART
2417     if (piechart) {
2418         im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2419         Xpie=im->piesize;
2420         Ypie=im->piesize;
2421     }
2422 #endif
2423
2424     /* Now calculate the total size.  Insert some spacing where
2425        desired.  im->xorigin and im->yorigin need to correspond
2426        with the lower left corner of the main graph area or, if
2427        this one is not set, the imaginary box surrounding the
2428        pie chart area. */
2429
2430     /* The legend width cannot yet be determined, as a result we
2431     ** have problems adjusting the image to it.  For now, we just
2432     ** forget about it at all; the legend will have to fit in the
2433     ** size already allocated.
2434     */
2435     im->ximg = Xylabel + Xmain + 2 * Xspacing;
2436
2437 #ifdef WITH_PIECHART
2438     im->ximg  += Xpie;
2439 #endif
2440
2441     if (Xmain) im->ximg += Xspacing;
2442 #ifdef WITH_PIECHART
2443     if (Xpie) im->ximg += Xspacing;
2444 #endif
2445
2446     im->xorigin = Xspacing + Xylabel;
2447
2448     /* the length of the title should not influence with width of the graph
2449        if (Xtitle > im->ximg) im->ximg = Xtitle; */
2450
2451     if (Xvertical) { /* unit description */
2452         im->ximg += Xvertical;
2453         im->xorigin += Xvertical;
2454     }
2455     xtr(im,0);
2456
2457     /* The vertical size is interesting... we need to compare
2458     ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with 
2459     ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2460     ** in order to start even thinking about Ylegend or Ywatermark.
2461     **
2462     ** Do it in three portions: First calculate the inner part,
2463     ** then do the legend, then adjust the total height of the img,
2464     ** adding space for a watermark if one exists;
2465     */
2466
2467     /* reserve space for main and/or pie */
2468
2469     im->yimg = Ymain + Yxlabel;
2470     
2471 #ifdef WITH_PIECHART
2472     if (im->yimg < Ypie) im->yimg = Ypie;
2473 #endif
2474
2475     im->yorigin = im->yimg - Yxlabel;
2476
2477     /* reserve space for the title *or* some padding above the graph */
2478     if (Ytitle) {
2479         im->yimg += Ytitle;
2480         im->yorigin += Ytitle;
2481     } else {
2482         im->yimg += 1.5*Yspacing;
2483         im->yorigin += 1.5*Yspacing;
2484     }
2485     /* reserve space for padding below the graph */
2486     im->yimg += Yspacing;
2487      
2488     /* Determine where to place the legends onto the image.
2489     ** Adjust im->yimg to match the space requirements.
2490     */
2491     if(leg_place(im)==-1)
2492         return -1;
2493         
2494     if (im->watermark[0] != '\0') {
2495         im->yimg += Ywatermark;
2496     }
2497
2498 #if 0
2499     if (Xlegend > im->ximg) {
2500         im->ximg = Xlegend;
2501         /* reposition Pie */
2502     }
2503 #endif
2504
2505 #ifdef WITH_PIECHART
2506     /* The pie is placed in the upper right hand corner,
2507     ** just below the title (if any) and with sufficient
2508     ** padding.
2509     */
2510     if (elements) {
2511         im->pie_x = im->ximg - Xspacing - Xpie/2;
2512         im->pie_y = im->yorigin-Ymain+Ypie/2;
2513     } else {
2514         im->pie_x = im->ximg/2;
2515         im->pie_y = im->yorigin-Ypie/2;
2516     }
2517 #endif
2518
2519     ytr(im,DNAN);
2520     return 0;
2521 }
2522
2523 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2524 /* yes we are loosing precision by doing tos with floats instead of doubles
2525    but it seems more stable this way. */
2526    
2527
2528 /* draw that picture thing ... */
2529 int
2530 graph_paint(image_desc_t *im, char ***calcpr)
2531 {
2532   int i,ii;
2533   int lazy =     lazy_check(im);
2534 #ifdef WITH_PIECHART
2535   int piechart = 0;
2536   double PieStart=0.0;
2537 #endif
2538   FILE  *fo;
2539   gfx_node_t *node;
2540   
2541   double areazero = 0.0;
2542   graph_desc_t *lastgdes = NULL;    
2543
2544   /* if we are lazy and there is nothing to PRINT ... quit now */
2545   if (lazy && im->prt_c==0) return 0;
2546
2547   /* pull the data from the rrd files ... */
2548   
2549   if(data_fetch(im)==-1)
2550     return -1;
2551
2552   /* evaluate VDEF and CDEF operations ... */
2553   if(data_calc(im)==-1)
2554     return -1;
2555
2556 #ifdef WITH_PIECHART  
2557   /* check if we need to draw a piechart */
2558   for(i=0;i<im->gdes_c;i++){
2559     if (im->gdes[i].gf == GF_PART) {
2560       piechart=1;
2561       break;
2562     }
2563   }
2564 #endif
2565
2566   /* calculate and PRINT and GPRINT definitions. We have to do it at
2567    * this point because it will affect the length of the legends
2568    * if there are no graph elements we stop here ... 
2569    * if we are lazy, try to quit ... 
2570    */
2571   i=print_calc(im,calcpr);
2572   if(i<0) return -1;
2573   if(((i==0)
2574 #ifdef WITH_PIECHART
2575 &&(piechart==0)
2576 #endif
2577 ) || lazy) return 0;
2578
2579 #ifdef WITH_PIECHART
2580   /* If there's only the pie chart to draw, signal this */
2581   if (i==0) piechart=2;
2582 #endif
2583   
2584   /* get actual drawing data and find min and max values*/
2585   if(data_proc(im)==-1)
2586     return -1;
2587   
2588   if(!im->logarithmic){si_unit(im);}        /* identify si magnitude Kilo, Mega Giga ? */
2589   
2590   if(!im->rigid && ! im->logarithmic)
2591     expand_range(im);   /* make sure the upper and lower limit are
2592                            sensible values */
2593
2594   if (!calc_horizontal_grid(im))
2595     return -1;
2596
2597   if (im->gridfit)
2598     apply_gridfit(im);
2599
2600
2601 /**************************************************************
2602  *** Calculating sizes and locations became a bit confusing ***
2603  *** so I moved this into a separate function.              ***
2604  **************************************************************/
2605   if(graph_size_location(im,i
2606 #ifdef WITH_PIECHART
2607 ,piechart
2608 #endif
2609 )==-1)
2610     return -1;
2611
2612   /* the actual graph is created by going through the individual
2613      graph elements and then drawing them */
2614   
2615   node=gfx_new_area ( im->canvas,
2616                       0, 0,
2617                       0, im->yimg,
2618                       im->ximg, im->yimg,                      
2619                       im->graph_col[GRC_BACK]);
2620
2621   gfx_add_point(node,im->ximg, 0);
2622
2623 #ifdef WITH_PIECHART
2624   if (piechart != 2) {
2625 #endif
2626     node=gfx_new_area ( im->canvas,
2627                       im->xorigin,             im->yorigin, 
2628                       im->xorigin + im->xsize, im->yorigin,
2629                       im->xorigin + im->xsize, im->yorigin-im->ysize,
2630                       im->graph_col[GRC_CANVAS]);
2631   
2632     gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2633
2634     if (im->minval > 0.0)
2635       areazero = im->minval;
2636     if (im->maxval < 0.0)
2637       areazero = im->maxval;
2638 #ifdef WITH_PIECHART
2639    }
2640 #endif
2641
2642 #ifdef WITH_PIECHART
2643   if (piechart) {
2644     pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2645   }
2646 #endif
2647
2648   for(i=0;i<im->gdes_c;i++){
2649     switch(im->gdes[i].gf){
2650     case GF_CDEF:
2651     case GF_VDEF:
2652     case GF_DEF:
2653     case GF_PRINT:
2654     case GF_GPRINT:
2655     case GF_COMMENT:
2656     case GF_HRULE:
2657     case GF_VRULE:
2658     case GF_XPORT:
2659     case GF_SHIFT:
2660       break;
2661     case GF_TICK:
2662       for (ii = 0; ii < im->xsize; ii++)
2663         {
2664           if (!isnan(im->gdes[i].p_data[ii]) && 
2665               im->gdes[i].p_data[ii] != 0.0)
2666            { 
2667               if (im -> gdes[i].yrule > 0 ) {
2668                       gfx_new_line(im->canvas,
2669                                    im -> xorigin + ii, im->yorigin,
2670                                    im -> xorigin + ii, im->yorigin - im -> gdes[i].yrule * im -> ysize,
2671                                    1.0,
2672                                    im -> gdes[i].col );
2673               } else if ( im -> gdes[i].yrule < 0 ) {
2674                       gfx_new_line(im->canvas,
2675                                    im -> xorigin + ii, im->yorigin - im -> ysize,
2676                                    im -> xorigin + ii, im->yorigin - ( 1 - im -> gdes[i].yrule ) * im -> ysize,
2677                                    1.0,
2678                                    im -> gdes[i].col );
2679               
2680               }
2681            }
2682         }
2683       break;
2684     case GF_LINE:
2685     case GF_AREA:
2686       /* fix data points at oo and -oo */
2687       for(ii=0;ii<im->xsize;ii++){
2688         if (isinf(im->gdes[i].p_data[ii])){
2689           if (im->gdes[i].p_data[ii] > 0) {
2690             im->gdes[i].p_data[ii] = im->maxval ;
2691           } else {
2692             im->gdes[i].p_data[ii] = im->minval ;
2693           }                 
2694           
2695         }
2696       } /* for */
2697
2698       /* *******************************************************
2699        a           ___. (a,t) 
2700                   |   |    ___
2701               ____|   |   |   |
2702               |       |___|
2703        -------|--t-1--t--------------------------------      
2704                       
2705       if we know the value at time t was a then 
2706       we draw a square from t-1 to t with the value a.
2707
2708       ********************************************************* */
2709       if (im->gdes[i].col != 0x0){   
2710         /* GF_LINE and friend */
2711         if(im->gdes[i].gf == GF_LINE ){
2712           double last_y=0.0;
2713           node = NULL;
2714           for(ii=1;ii<im->xsize;ii++){
2715             if (isnan(im->gdes[i].p_data[ii]) || (im->slopemode==1 && isnan(im->gdes[i].p_data[ii-1]))){
2716                 node = NULL;
2717                 continue;
2718             }
2719             if ( node == NULL ) {
2720                 last_y = ytr(im,im->gdes[i].p_data[ii]);
2721                 if ( im->slopemode == 0 ){
2722                   node = gfx_new_line(im->canvas,
2723                                     ii-1+im->xorigin,last_y,
2724                                     ii+im->xorigin,last_y,
2725                                     im->gdes[i].linewidth,
2726                                     im->gdes[i].col);
2727                 } else {
2728                   node = gfx_new_line(im->canvas,
2729                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2730                                     ii+im->xorigin,last_y,
2731                                     im->gdes[i].linewidth,
2732                                     im->gdes[i].col);
2733                 }
2734              } else {
2735                double new_y = ytr(im,im->gdes[i].p_data[ii]);
2736                if ( im->slopemode==0 && ! AlmostEqual2sComplement(new_y,last_y,4)){
2737                    gfx_add_point(node,ii-1+im->xorigin,new_y);
2738                };
2739                last_y = new_y;
2740                gfx_add_point(node,ii+im->xorigin,new_y);
2741              };
2742
2743           }
2744         } else {
2745           int idxI=-1;
2746           double *foreY=malloc(sizeof(double)*im->xsize*2);
2747           double *foreX=malloc(sizeof(double)*im->xsize*2);
2748           double *backY=malloc(sizeof(double)*im->xsize*2);
2749           double *backX=malloc(sizeof(double)*im->xsize*2);
2750           int drawem = 0;
2751           for(ii=0;ii<=im->xsize;ii++){
2752             double ybase,ytop;
2753             if ( idxI > 0 && ( drawem != 0 || ii==im->xsize)){
2754                int cntI=1;
2755                int lastI=0;
2756                while (cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;}
2757                node = gfx_new_area(im->canvas,
2758                                 backX[0],backY[0],
2759                                 foreX[0],foreY[0],
2760                                 foreX[cntI],foreY[cntI], im->gdes[i].col);
2761                while (cntI < idxI) {
2762                  lastI = cntI;
2763                  cntI++;
2764                  while ( cntI < idxI && AlmostEqual2sComplement(foreY[lastI],foreY[cntI],4) && AlmostEqual2sComplement(foreY[lastI],foreY[cntI+1],4)){cntI++;} 
2765                  gfx_add_point(node,foreX[cntI],foreY[cntI]);
2766                }
2767                gfx_add_point(node,backX[idxI],backY[idxI]);
2768                while (idxI > 1){
2769                  lastI = idxI;
2770                  idxI--;
2771                  while ( idxI > 1 && AlmostEqual2sComplement(backY[lastI], backY[idxI],4) && AlmostEqual2sComplement(backY[lastI],backY[idxI-1],4)){idxI--;} 
2772                  gfx_add_point(node,backX[idxI],backY[idxI]);
2773                }
2774                idxI=-1;
2775                drawem = 0;
2776             }
2777             if (drawem != 0){
2778               drawem = 0;
2779               idxI=-1;
2780             }
2781             if (ii == im->xsize) break;
2782             
2783             /* keep things simple for now, just draw these bars
2784                do not try to build a big and complex area */
2785
2786                                                               
2787             if ( im->slopemode == 0 && ii==0){
2788                 continue;
2789             }
2790             if ( isnan(im->gdes[i].p_data[ii]) ) {
2791                 drawem = 1;
2792                 continue;
2793             }
2794             ytop = ytr(im,im->gdes[i].p_data[ii]);
2795             if ( lastgdes && im->gdes[i].stack ) {
2796                   ybase = ytr(im,lastgdes->p_data[ii]);
2797             } else {
2798                   ybase = ytr(im,areazero);
2799             }
2800             if ( ybase == ytop ){
2801                 drawem = 1;
2802                 continue;       
2803             }
2804             /* every area has to be wound clock-wise,
2805                so we have to make sur base remains base  */             
2806             if (ybase > ytop){
2807                 double extra = ytop;
2808                 ytop = ybase;
2809                 ybase = extra;
2810             }
2811             if ( im->slopemode == 0 ){
2812                     backY[++idxI] = ybase-0.2;
2813                     backX[idxI] = ii+im->xorigin-1;
2814                     foreY[idxI] = ytop+0.2;
2815                     foreX[idxI] = ii+im->xorigin-1;
2816             }
2817             backY[++idxI] = ybase-0.2;
2818             backX[idxI] = ii+im->xorigin;
2819             foreY[idxI] = ytop+0.2;
2820             foreX[idxI] = ii+im->xorigin;
2821           }
2822           /* close up any remaining area */             
2823           free(foreY);
2824           free(foreX);
2825           free(backY);
2826           free(backX);
2827         } /* else GF_LINE */
2828       } /* if color != 0x0 */
2829       /* make sure we do not run into trouble when stacking on NaN */
2830       for(ii=0;ii<im->xsize;ii++){
2831         if (isnan(im->gdes[i].p_data[ii])) {
2832           if (lastgdes && (im->gdes[i].stack)) {
2833             im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
2834           } else {
2835             im->gdes[i].p_data[ii] = areazero;
2836           }
2837         }
2838       } 
2839       lastgdes = &(im->gdes[i]);                         
2840       break;
2841 #ifdef WITH_PIECHART
2842     case GF_PART:
2843       if(isnan(im->gdes[i].yrule)) /* fetch variable */
2844         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2845      
2846       if (finite(im->gdes[i].yrule)) {  /* even the fetched var can be NaN */
2847         pie_part(im,im->gdes[i].col,
2848                 im->pie_x,im->pie_y,im->piesize*0.4,
2849                 M_PI*2.0*PieStart/100.0,
2850                 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2851         PieStart += im->gdes[i].yrule;
2852       }
2853       break;
2854 #endif
2855     case GF_STACK:
2856       rrd_set_error("STACK should already be turned into LINE or AREA here");
2857       return -1;
2858       break;
2859         
2860     } /* switch */
2861   }
2862 #ifdef WITH_PIECHART
2863   if (piechart==2) {
2864     im->draw_x_grid=0;
2865     im->draw_y_grid=0;
2866   }
2867 #endif
2868
2869
2870   /* grid_paint also does the text */
2871   if( !(im->extra_flags & ONLY_GRAPH) )  
2872     grid_paint(im);
2873
2874   
2875   if( !(im->extra_flags & ONLY_GRAPH) )  
2876       axis_paint(im);
2877   
2878   /* the RULES are the last thing to paint ... */
2879   for(i=0;i<im->gdes_c;i++){    
2880     
2881     switch(im->gdes[i].gf){
2882     case GF_HRULE:
2883       if(im->gdes[i].yrule >= im->minval
2884          && im->gdes[i].yrule <= im->maxval)
2885         gfx_new_line(im->canvas,
2886                      im->xorigin,ytr(im,im->gdes[i].yrule),
2887                      im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2888                      1.0,im->gdes[i].col); 
2889       break;
2890     case GF_VRULE:
2891       if(im->gdes[i].xrule >= im->start
2892          && im->gdes[i].xrule <= im->end)
2893         gfx_new_line(im->canvas,
2894                      xtr(im,im->gdes[i].xrule),im->yorigin,
2895                      xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2896                      1.0,im->gdes[i].col); 
2897       break;
2898     default:
2899       break;
2900     }
2901   }
2902
2903   
2904   if (strcmp(im->graphfile,"-")==0) {
2905     fo = im->graphhandle ? im->graphhandle : stdout;
2906 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
2907     /* Change translation mode for stdout to BINARY */
2908     _setmode( _fileno( fo ), O_BINARY );
2909 #endif
2910   } else {
2911     if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2912       rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2913                     rrd_strerror(errno));
2914       return (-1);
2915     }
2916   }
2917   gfx_render (im->canvas,im->ximg,im->yimg,0x00000000,fo);
2918   if (strcmp(im->graphfile,"-") != 0)
2919     fclose(fo);
2920   return 0;
2921 }
2922
2923
2924 /*****************************************************
2925  * graph stuff 
2926  *****************************************************/
2927
2928 int
2929 gdes_alloc(image_desc_t *im){
2930
2931     im->gdes_c++;
2932     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2933                                            * sizeof(graph_desc_t)))==NULL){
2934         rrd_set_error("realloc graph_descs");
2935         return -1;
2936     }
2937
2938
2939     im->gdes[im->gdes_c-1].step=im->step;
2940     im->gdes[im->gdes_c-1].step_orig=im->step;
2941     im->gdes[im->gdes_c-1].stack=0;
2942     im->gdes[im->gdes_c-1].linewidth=0;
2943     im->gdes[im->gdes_c-1].debug=0;
2944     im->gdes[im->gdes_c-1].start=im->start; 
2945     im->gdes[im->gdes_c-1].start_orig=im->start; 
2946     im->gdes[im->gdes_c-1].end=im->end; 
2947     im->gdes[im->gdes_c-1].end_orig=im->end; 
2948     im->gdes[im->gdes_c-1].vname[0]='\0'; 
2949     im->gdes[im->gdes_c-1].data=NULL;
2950     im->gdes[im->gdes_c-1].ds_namv=NULL;
2951     im->gdes[im->gdes_c-1].data_first=0;
2952     im->gdes[im->gdes_c-1].p_data=NULL;
2953     im->gdes[im->gdes_c-1].rpnp=NULL;
2954     im->gdes[im->gdes_c-1].shift=0;
2955     im->gdes[im->gdes_c-1].col = 0x0;
2956     im->gdes[im->gdes_c-1].legend[0]='\0';
2957     im->gdes[im->gdes_c-1].format[0]='\0';
2958     im->gdes[im->gdes_c-1].strftm=0;   
2959     im->gdes[im->gdes_c-1].rrd[0]='\0';
2960     im->gdes[im->gdes_c-1].ds=-1;    
2961     im->gdes[im->gdes_c-1].cf_reduce=CF_AVERAGE;    
2962     im->gdes[im->gdes_c-1].cf=CF_AVERAGE;    
2963     im->gdes[im->gdes_c-1].p_data=NULL;    
2964     im->gdes[im->gdes_c-1].yrule=DNAN;
2965     im->gdes[im->gdes_c-1].xrule=0;
2966     return 0;
2967 }
2968
2969 /* copies input untill the first unescaped colon is found
2970    or until input ends. backslashes have to be escaped as well */
2971 int
2972 scan_for_col(const char *const input, int len, char *const output)
2973 {
2974     int inp,outp=0;
2975     for (inp=0; 
2976          inp < len &&
2977            input[inp] != ':' &&
2978            input[inp] != '\0';
2979          inp++){
2980       if (input[inp] == '\\' &&
2981           input[inp+1] != '\0' && 
2982           (input[inp+1] == '\\' ||
2983            input[inp+1] == ':')){
2984         output[outp++] = input[++inp];
2985       }
2986       else {
2987         output[outp++] = input[inp];
2988       }
2989     }
2990     output[outp] = '\0';
2991     return inp;
2992 }
2993 /* Some surgery done on this function, it became ridiculously big.
2994 ** Things moved:
2995 ** - initializing     now in rrd_graph_init()
2996 ** - options parsing  now in rrd_graph_options()
2997 ** - script parsing   now in rrd_graph_script()
2998 */
2999 int 
3000 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize, FILE *stream, double *ymin, double *ymax)
3001 {
3002     image_desc_t   im;
3003     rrd_graph_init(&im);
3004     im.graphhandle = stream;
3005     
3006     rrd_graph_options(argc,argv,&im);
3007     if (rrd_test_error()) {
3008         im_free(&im);
3009         return -1;
3010     }
3011     
3012     if (strlen(argv[optind])>=MAXPATH) {
3013         rrd_set_error("filename (including path) too long");
3014         im_free(&im);
3015         return -1;
3016     }
3017     strncpy(im.graphfile,argv[optind],MAXPATH-1);
3018     im.graphfile[MAXPATH-1]='\0';
3019
3020     rrd_graph_script(argc,argv,&im,1);
3021     if (rrd_test_error()) {
3022         im_free(&im);
3023         return -1;
3024     }
3025
3026     /* Everything is now read and the actual work can start */
3027
3028     (*prdata)=NULL;
3029     if (graph_paint(&im,prdata)==-1){
3030         im_free(&im);
3031         return -1;
3032     }
3033
3034     /* The image is generated and needs to be output.
3035     ** Also, if needed, print a line with information about the image.
3036     */
3037
3038     *xsize=im.ximg;
3039     *ysize=im.yimg;
3040     *ymin=im.minval;
3041     *ymax=im.maxval;
3042     if (im.imginfo) {
3043         char *filename;
3044         if (!(*prdata)) {
3045             /* maybe prdata is not allocated yet ... lets do it now */
3046             if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
3047                 rrd_set_error("malloc imginfo");
3048                 return -1; 
3049             };
3050         }
3051         if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
3052          ==NULL){
3053             rrd_set_error("malloc imginfo");
3054             return -1;
3055         }
3056         filename=im.graphfile+strlen(im.graphfile);
3057         while(filename > im.graphfile) {
3058             if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
3059             filename--;
3060         }
3061
3062         sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
3063     }
3064     im_free(&im);
3065     return 0;
3066 }
3067
3068 void
3069 rrd_graph_init(image_desc_t *im)
3070 {
3071     unsigned int i;
3072
3073 #ifdef HAVE_TZSET
3074     tzset();
3075 #endif
3076 #ifdef HAVE_SETLOCALE
3077     setlocale(LC_TIME,"");
3078 #ifdef HAVE_MBSTOWCS
3079     setlocale(LC_CTYPE,"");
3080 #endif
3081 #endif
3082     im->yorigin=0;
3083     im->xorigin=0;
3084     im->minval=0;
3085     im->xlab_user.minsec = -1;
3086     im->ximg=0;
3087     im->yimg=0;
3088     im->xsize = 400;
3089     im->ysize = 100;
3090     im->step = 0;
3091     im->ylegend[0] = '\0';
3092     im->title[0] = '\0';
3093     im->watermark[0] = '\0';
3094     im->minval = DNAN;
3095     im->maxval = DNAN;    
3096     im->unitsexponent= 9999;
3097     im->unitslength= 6; 
3098     im->symbol = ' ';
3099     im->viewfactor = 1.0;
3100     im->extra_flags= 0;
3101     im->rigid = 0;
3102     im->gridfit = 1;
3103     im->imginfo = NULL;
3104     im->lazy = 0;
3105     im->slopemode = 0;
3106     im->logarithmic = 0;
3107     im->ygridstep = DNAN;
3108     im->draw_x_grid = 1;
3109     im->draw_y_grid = 1;
3110     im->base = 1000;
3111     im->prt_c = 0;
3112     im->gdes_c = 0;
3113     im->gdes = NULL;
3114     im->canvas = gfx_new_canvas();
3115     im->grid_dash_on = 1;
3116     im->grid_dash_off = 1;
3117     im->tabwidth = 40.0;
3118     
3119     for(i=0;i<DIM(graph_col);i++)
3120         im->graph_col[i]=graph_col[i];
3121
3122 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3123     {
3124             char *windir; 
3125             char rrd_win_default_font[1000];
3126             windir = getenv("windir");
3127             /* %windir% is something like D:\windows or C:\winnt */
3128             if (windir != NULL) {
3129                     strncpy(rrd_win_default_font,windir,500);
3130                     rrd_win_default_font[500] = '\0';
3131                     strcat(rrd_win_default_font,"\\fonts\\");
3132                     strcat(rrd_win_default_font,RRD_DEFAULT_FONT);         
3133                     for(i=0;i<DIM(text_prop);i++){
3134                             strncpy(text_prop[i].font,rrd_win_default_font,sizeof(text_prop[i].font)-1);
3135                             text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3136                      }
3137              }
3138     }
3139 #endif
3140     {
3141             char *deffont; 
3142             deffont = getenv("RRD_DEFAULT_FONT");
3143             if (deffont != NULL) {
3144                  for(i=0;i<DIM(text_prop);i++){
3145                         strncpy(text_prop[i].font,deffont,sizeof(text_prop[i].font)-1);
3146                         text_prop[i].font[sizeof(text_prop[i].font)-1] = '\0';
3147                  }
3148             }
3149     }
3150     for(i=0;i<DIM(text_prop);i++){        
3151       im->text_prop[i].size = text_prop[i].size;
3152       strcpy(im->text_prop[i].font,text_prop[i].font);
3153     }
3154 }
3155
3156 void
3157 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
3158 {
3159     int                 stroff;    
3160     char                *parsetime_error = NULL;
3161     char                scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
3162     time_t              start_tmp=0,end_tmp=0;
3163     long                long_tmp;
3164     struct rrd_time_value       start_tv, end_tv;
3165     gfx_color_t         color;
3166     optind = 0; opterr = 0;  /* initialize getopt */
3167
3168     parsetime("end-24h", &start_tv);
3169     parsetime("now", &end_tv);
3170
3171     /* defines for long options without a short equivalent. should be bytes,
3172        and may not collide with (the ASCII value of) short options */
3173     #define LONGOPT_UNITS_SI 255
3174
3175     while (1){
3176         static struct option long_options[] =
3177         {
3178             {"start",      required_argument, 0,  's'},
3179             {"end",        required_argument, 0,  'e'},
3180             {"x-grid",     required_argument, 0,  'x'},
3181             {"y-grid",     required_argument, 0,  'y'},
3182             {"vertical-label",required_argument,0,'v'},
3183             {"width",      required_argument, 0,  'w'},
3184             {"height",     required_argument, 0,  'h'},
3185             {"interlaced", no_argument,       0,  'i'},
3186             {"upper-limit",required_argument, 0,  'u'},
3187             {"lower-limit",required_argument, 0,  'l'},
3188             {"rigid",      no_argument,       0,  'r'},
3189             {"base",       required_argument, 0,  'b'},
3190             {"logarithmic",no_argument,       0,  'o'},
3191             {"color",      required_argument, 0,  'c'},
3192             {"font",       required_argument, 0,  'n'},
3193             {"title",      required_argument, 0,  't'},
3194             {"imginfo",    required_argument, 0,  'f'},
3195             {"imgformat",  required_argument, 0,  'a'},
3196             {"lazy",       no_argument,       0,  'z'},
3197             {"zoom",       required_argument, 0,  'm'},
3198             {"no-legend",  no_argument,       0,  'g'},
3199             {"force-rules-legend",no_argument,0,  'F'},
3200             {"only-graph", no_argument,       0,  'j'},
3201             {"alt-y-grid", no_argument,       0,  'Y'},
3202             {"no-minor",   no_argument,       0,  'I'},
3203             {"slope-mode", no_argument,       0,  'E'},
3204             {"alt-autoscale", no_argument,    0,  'A'},
3205             {"alt-autoscale-max", no_argument, 0, 'M'},
3206             {"no-gridfit", no_argument,       0,   'N'},
3207             {"units-exponent",required_argument, 0, 'X'},
3208             {"units-length",required_argument, 0, 'L'},
3209             {"units",      required_argument, 0,  LONGOPT_UNITS_SI },
3210             {"step",       required_argument, 0,    'S'},
3211             {"tabwidth",   required_argument, 0,    'T'},            
3212             {"font-render-mode", required_argument, 0, 'R'},
3213             {"font-smoothing-threshold", required_argument, 0, 'B'},
3214             {"watermark",  required_argument, 0,  'W'},
3215             {"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 */
3216             {0,0,0,0}};
3217         int option_index = 0;
3218         int opt;
3219         int col_start,col_end;
3220
3221         opt = getopt_long(argc, argv, 
3222                          "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3223                           long_options, &option_index);
3224
3225         if (opt == EOF)
3226             break;
3227         
3228         switch(opt) {
3229         case 'I':
3230             im->extra_flags |= NOMINOR;
3231             break;
3232         case 'Y':
3233             im->extra_flags |= ALTYGRID;
3234             break;
3235         case 'A':
3236             im->extra_flags |= ALTAUTOSCALE;
3237             break;
3238         case 'M':
3239             im->extra_flags |= ALTAUTOSCALE_MAX;
3240             break;
3241         case 'j':
3242            im->extra_flags |= ONLY_GRAPH;
3243            break;
3244         case 'g':
3245             im->extra_flags |= NOLEGEND;
3246             break;
3247         case 'F':
3248             im->extra_flags |= FORCE_RULES_LEGEND;
3249             break;
3250         case LONGOPT_UNITS_SI:
3251             if(im->extra_flags & FORCE_UNITS) {
3252                 rrd_set_error("--units can only be used once!");
3253                 return;
3254             }
3255             if(strcmp(optarg,"si")==0)
3256                 im->extra_flags |= FORCE_UNITS_SI;
3257             else {
3258                 rrd_set_error("invalid argument for --units: %s", optarg );
3259                 return;
3260             }
3261             break;
3262         case 'X':
3263             im->unitsexponent = atoi(optarg);
3264             break;
3265         case 'L':
3266             im->unitslength = atoi(optarg);
3267             break;
3268         case 'T':
3269             im->tabwidth = atof(optarg);
3270             break;
3271         case 'S':
3272             im->step =  atoi(optarg);
3273             break;
3274         case 'N':
3275             im->gridfit = 0;
3276             break;
3277         case 's':
3278             if ((parsetime_error = parsetime(optarg, &start_tv))) {
3279                 rrd_set_error( "start time: %s", parsetime_error );
3280                 return;
3281             }
3282             break;
3283         case 'e':
3284             if ((parsetime_error = parsetime(optarg, &end_tv))) {
3285                 rrd_set_error( "end time: %s", parsetime_error );
3286                 return;
3287             }
3288             break;
3289         case 'x':
3290             if(strcmp(optarg,"none") == 0){
3291               im->draw_x_grid=0;
3292               break;
3293             };
3294                 
3295             if(sscanf(optarg,
3296                       "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3297                       scan_gtm,
3298                       &im->xlab_user.gridst,
3299                       scan_mtm,
3300                       &im->xlab_user.mgridst,
3301                       scan_ltm,
3302                       &im->xlab_user.labst,
3303                       &im->xlab_user.precis,
3304                       &stroff) == 7 && stroff != 0){
3305                 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
3306                 im->xlab_form[sizeof(im->xlab_form)-1] = '\0'; 
3307                 if((int)(im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
3308                     rrd_set_error("unknown keyword %s",scan_gtm);
3309                     return;
3310                 } else if ((int)(im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
3311                     rrd_set_error("unknown keyword %s",scan_mtm);
3312                     return;
3313                 } else if ((int)(im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
3314                     rrd_set_error("unknown keyword %s",scan_ltm);
3315                     return;
3316                 } 
3317                 im->xlab_user.minsec = 1;
3318                 im->xlab_user.stst = im->xlab_form;
3319             } else {
3320                 rrd_set_error("invalid x-grid format");
3321                 return;
3322             }
3323             break;
3324         case 'y':
3325
3326             if(strcmp(optarg,"none") == 0){
3327               im->draw_y_grid=0;
3328               break;
3329             };
3330
3331             if(sscanf(optarg,
3332                       "%lf:%d",
3333                       &im->ygridstep,
3334                       &im->ylabfact) == 2) {
3335                 if(im->ygridstep<=0){
3336                     rrd_set_error("grid step must be > 0");
3337                     return;
3338                 } else if (im->ylabfact < 1){
3339                     rrd_set_error("label factor must be > 0");
3340                     return;
3341                 } 
3342             } else {
3343                 rrd_set_error("invalid y-grid format");
3344                 return;
3345             }
3346             break;
3347         case 'v':
3348             strncpy(im->ylegend,optarg,150);
3349             im->ylegend[150]='\0';
3350             break;
3351         case 'u':
3352             im->maxval = atof(optarg);
3353             break;
3354         case 'l':
3355             im->minval = atof(optarg);
3356             break;
3357         case 'b':
3358             im->base = atol(optarg);
3359             if(im->base != 1024 && im->base != 1000 ){
3360                 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
3361                 return;
3362             }
3363             break;
3364         case 'w':
3365             long_tmp = atol(optarg);
3366             if (long_tmp < 10) {
3367                 rrd_set_error("width below 10 pixels");
3368                 return;
3369             }
3370             im->xsize = long_tmp;
3371             break;
3372         case 'h':
3373             long_tmp = atol(optarg);
3374             if (long_tmp < 10) {
3375                 rrd_set_error("height below 10 pixels");
3376                 return;
3377             }
3378             im->ysize = long_tmp;
3379             break;
3380         case 'i':
3381             im->canvas->interlaced = 1;
3382             break;
3383         case 'r':
3384             im->rigid = 1;
3385             break;
3386         case 'f':
3387             im->imginfo = optarg;
3388             break;
3389         case 'a':
3390             if((int)(im->canvas->imgformat = if_conv(optarg)) == -1) {
3391                 rrd_set_error("unsupported graphics format '%s'",optarg);
3392                 return;
3393             }
3394             break;
3395         case 'z':
3396             im->lazy = 1;
3397             break;
3398         case 'E':
3399             im->slopemode = 1;
3400             break;
3401
3402         case 'o':
3403             im->logarithmic = 1;
3404             break;
3405         case 'c':
3406             if(sscanf(optarg,
3407                       "%10[A-Z]#%n%8lx%n",
3408                       col_nam,&col_start,&color,&col_end) == 2){
3409                 int ci;
3410                 int col_len = col_end - col_start;
3411                 switch (col_len){
3412                         case 3:
3413                                 color = (
3414                                         ((color & 0xF00) * 0x110000) |
3415                                         ((color & 0x0F0) * 0x011000) |
3416                                         ((color & 0x00F) * 0x001100) |
3417                                         0x000000FF
3418                                         );
3419                                 break;
3420                         case 4:
3421                                 color = (
3422                                         ((color & 0xF000) * 0x11000) |
3423                                         ((color & 0x0F00) * 0x01100) |
3424                                         ((color & 0x00F0) * 0x00110) |
3425                                         ((color & 0x000F) * 0x00011)
3426                                         );
3427                                 break;
3428                         case 6:
3429                                 color = (color << 8) + 0xff /* shift left by 8 */;
3430                                 break;
3431                         case 8:
3432                                 break;
3433                         default:
3434                                 rrd_set_error("the color format is #RRGGBB[AA]");
3435                                 return;
3436                 }
3437                 if((ci=grc_conv(col_nam)) != -1){
3438                     im->graph_col[ci]=color;
3439                 }  else {
3440                   rrd_set_error("invalid color name '%s'",col_nam);
3441                   return;
3442                 }
3443             } else {
3444                 rrd_set_error("invalid color def format");
3445                 return;
3446             }
3447             break;        
3448         case 'n':{
3449             char prop[15];
3450             double size = 1;
3451             char font[1024] = "";
3452
3453             if(sscanf(optarg,
3454                                 "%10[A-Z]:%lf:%1000s",
3455                                 prop,&size,font) >= 2){
3456                 int sindex,propidx;
3457                 if((sindex=text_prop_conv(prop)) != -1){
3458                   for (propidx=sindex;propidx<TEXT_PROP_LAST;propidx++){                      
3459                       if (size > 0){
3460                           im->text_prop[propidx].size=size;              
3461                       }
3462                       if (strlen(font) > 0){
3463                           strcpy(im->text_prop[propidx].font,font);
3464                       }
3465                       if (propidx==sindex && sindex != 0) break;
3466                   }
3467                 } else {
3468                     rrd_set_error("invalid fonttag '%s'",prop);
3469                     return;
3470                 }
3471             } else {
3472                 rrd_set_error("invalid text property format");
3473                 return;
3474             }
3475             break;          
3476         }
3477         case 'm':
3478             im->canvas->zoom = atof(optarg);
3479             if (im->canvas->zoom <= 0.0) {
3480                 rrd_set_error("zoom factor must be > 0");
3481                 return;
3482             }
3483           break;
3484         case 't':
3485             strncpy(im->title,optarg,150);
3486             im->title[150]='\0';
3487             break;
3488
3489         case 'R':
3490                 if ( strcmp( optarg, "normal" ) == 0 )
3491                         im->canvas->aa_type = AA_NORMAL;
3492                 else if ( strcmp( optarg, "light" ) == 0 )
3493                         im->canvas->aa_type = AA_LIGHT;
3494                 else if ( strcmp( optarg, "mono" ) == 0 )
3495                         im->canvas->aa_type = AA_NONE;
3496                 else
3497                 {
3498                         rrd_set_error("unknown font-render-mode '%s'", optarg );
3499                         return;
3500                 }
3501                 break;
3502
3503         case 'B':
3504             im->canvas->font_aa_threshold = atof(optarg);
3505                 break;
3506
3507         case 'W':
3508             strncpy(im->watermark,optarg,100);
3509             im->watermark[99]='\0';
3510             break;
3511
3512         case '?':
3513             if (optopt != 0)
3514                 rrd_set_error("unknown option '%c'", optopt);
3515             else
3516                 rrd_set_error("unknown option '%s'",argv[optind-1]);
3517             return;
3518         }
3519     }
3520     
3521     if (optind >= argc) {
3522        rrd_set_error("missing filename");
3523        return;
3524     }
3525
3526     if (im->logarithmic == 1 && im->minval <= 0){
3527         rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");    
3528         return;
3529     }
3530
3531     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3532         /* error string is set in parsetime.c */
3533         return;
3534     }  
3535     
3536     if (start_tmp < 3600*24*365*10){
3537         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3538         return;
3539     }
3540     
3541     if (end_tmp < start_tmp) {
3542         rrd_set_error("start (%ld) should be less than end (%ld)", 
3543                start_tmp, end_tmp);
3544         return;
3545     }
3546     
3547     im->start = start_tmp;
3548     im->end = end_tmp;
3549     im->step = max((long)im->step, (im->end-im->start)/im->xsize);
3550 }
3551
3552 int
3553 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3554 {
3555     if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3556         rrd_set_error("Unknown variable '%s' in %s",varname,err);
3557         return -1;
3558     }
3559     return 0;
3560 }
3561 int
3562 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3563 {
3564     char *color;
3565     graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3566
3567     color=strstr(var,"#");
3568     if (color==NULL) {
3569         if (optional==0) {
3570             rrd_set_error("Found no color in %s",err);
3571             return 0;
3572         }
3573         return 0;
3574     } else {
3575         int n=0;
3576         char *rest;
3577         gfx_color_t    col;
3578
3579         rest=strstr(color,":");
3580         if (rest!=NULL)
3581             n=rest-color;
3582         else
3583             n=strlen(color);
3584
3585         switch (n) {
3586             case 7:
3587                 sscanf(color,"#%6lx%n",&col,&n);
3588                 col = (col << 8) + 0xff /* shift left by 8 */;
3589                 if (n!=7) rrd_set_error("Color problem in %s",err);
3590                 break;
3591             case 9:
3592                 sscanf(color,"#%8lx%n",&col,&n);
3593                 if (n==9) break;
3594             default:
3595                 rrd_set_error("Color problem in %s",err);
3596         }
3597         if (rrd_test_error()) return 0;
3598         gdp->col = col;
3599         return n;
3600     }
3601 }
3602
3603
3604 int bad_format(char *fmt) {
3605     char *ptr;
3606     int n=0;
3607     ptr = fmt;
3608     while (*ptr != '\0')
3609         if (*ptr++ == '%') {
3610  
3611              /* line cannot end with percent char */
3612              if (*ptr == '\0') return 1;
3613  
3614              /* '%s', '%S' and '%%' are allowed */
3615              if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3616
3617              /* %c is allowed (but use only with vdef!) */
3618              else if (*ptr == 'c') {
3619                 ptr++;
3620                 n=1;
3621              }
3622
3623              /* or else '% 6.2lf' and such are allowed */
3624              else {
3625                  /* optional padding character */
3626                  if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3627
3628                  /* This should take care of 'm.n' with all three optional */
3629                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3630                  if (*ptr == '.') ptr++;
3631                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3632   
3633                  /* Either 'le', 'lf' or 'lg' must follow here */
3634                  if (*ptr++ != 'l') return 1;
3635                  if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g') ptr++;
3636                  else return 1;
3637                  n++;
3638             }
3639          }
3640       
3641       return (n!=1); 
3642 }
3643
3644
3645 int
3646 vdef_parse(gdes,str)
3647 struct graph_desc_t *gdes;
3648 const char *const str;
3649 {
3650     /* A VDEF currently is either "func" or "param,func"
3651      * so the parsing is rather simple.  Change if needed.
3652      */
3653     double      param;
3654     char        func[30];
3655     int         n;
3656     
3657     n=0;
3658     sscanf(str,"%le,%29[A-Z]%n",&param,func,&n);
3659     if (n== (int)strlen(str)) { /* matched */
3660         ;
3661     } else {
3662         n=0;
3663         sscanf(str,"%29[A-Z]%n",func,&n);
3664         if (n== (int)strlen(str)) { /* matched */
3665             param=DNAN;
3666         } else {
3667             rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3668                 ,str
3669                 ,gdes->vname
3670                 );
3671             return -1;
3672         }
3673     }
3674     if          (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3675     else if     (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3676     else if     (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3677     else if     (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3678     else if     (!strcmp("TOTAL",  func)) gdes->vf.op = VDEF_TOTAL;
3679     else if     (!strcmp("FIRST",  func)) gdes->vf.op = VDEF_FIRST;
3680     else if     (!strcmp("LAST",   func)) gdes->vf.op = VDEF_LAST;
3681     else if     (!strcmp("LSLSLOPE", func)) gdes->vf.op = VDEF_LSLSLOPE;
3682     else if     (!strcmp("LSLINT",   func)) gdes->vf.op = VDEF_LSLINT;
3683     else if     (!strcmp("LSLCORREL",func)) gdes->vf.op = VDEF_LSLCORREL;
3684     else {
3685         rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3686             ,func
3687             ,gdes->vname
3688             );
3689         return -1;
3690     };
3691
3692     switch (gdes->vf.op) {
3693         case VDEF_PERCENT:
3694             if (isnan(param)) { /* no parameter given */
3695                 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3696                     ,func
3697                     ,gdes->vname
3698                     );
3699                 return -1;
3700             };
3701             if (param>=0.0 && param<=100.0) {
3702                 gdes->vf.param = param;
3703                 gdes->vf.val   = DNAN;  /* undefined */
3704                 gdes->vf.when  = 0;     /* undefined */
3705             } else {
3706                 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3707                     ,param
3708                     ,gdes->vname
3709                     );
3710                 return -1;
3711             };
3712             break;
3713         case VDEF_MAXIMUM:
3714         case VDEF_AVERAGE:
3715         case VDEF_MINIMUM:
3716         case VDEF_TOTAL:
3717         case VDEF_FIRST:
3718         case VDEF_LAST:
3719         case VDEF_LSLSLOPE:
3720         case VDEF_LSLINT:
3721         case VDEF_LSLCORREL:
3722             if (isnan(param)) {
3723                 gdes->vf.param = DNAN;
3724                 gdes->vf.val   = DNAN;
3725                 gdes->vf.when  = 0;
3726             } else {
3727                 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3728                     ,func
3729                     ,gdes->vname
3730                     );
3731                 return -1;
3732             };
3733             break;
3734     };
3735     return 0;
3736 }
3737
3738
3739 int
3740 vdef_calc(im,gdi)
3741 image_desc_t *im;
3742 int gdi;
3743 {
3744     graph_desc_t        *src,*dst;
3745     rrd_value_t         *data;
3746     long                step,steps;
3747
3748     dst = &im->gdes[gdi];
3749     src = &im->gdes[dst->vidx];
3750     data = src->data + src->ds;
3751     steps = (src->end - src->start) / src->step;
3752
3753 #if 0
3754 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3755     ,src->start
3756     ,src->end
3757     ,steps
3758     );
3759 #endif
3760
3761     switch (dst->vf.op) {
3762         case VDEF_PERCENT: {
3763                 rrd_value_t *   array;
3764                 int             field;
3765
3766
3767                 if ((array = malloc(steps*sizeof(double)))==NULL) {
3768                     rrd_set_error("malloc VDEV_PERCENT");
3769                     return -1;
3770                 }
3771                 for (step=0;step < steps; step++) {
3772                     array[step]=data[step*src->ds_cnt];
3773                 }
3774                 qsort(array,step,sizeof(double),vdef_percent_compar);
3775
3776                 field = (steps-1)*dst->vf.param/100;
3777                 dst->vf.val  = array[field];
3778                 dst->vf.when = 0;       /* no time component */
3779                 free(array);
3780 #if 0
3781 for(step=0;step<steps;step++)
3782 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3783 #endif
3784             }
3785             break;
3786         case VDEF_MAXIMUM:
3787             step=0;
3788             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3789             if (step == steps) {
3790                 dst->vf.val  = DNAN;
3791                 dst->vf.when = 0;
3792             } else {
3793                 dst->vf.val  = data[step*src->ds_cnt];
3794                 dst->vf.when = src->start + (step+1)*src->step;
3795             }
3796             while (step != steps) {
3797                 if (finite(data[step*src->ds_cnt])) {
3798                     if (data[step*src->ds_cnt] > dst->vf.val) {
3799                         dst->vf.val  = data[step*src->ds_cnt];
3800                         dst->vf.when = src->start + (step+1)*src->step;
3801                     }
3802                 }
3803                 step++;
3804             }
3805             break;
3806         case VDEF_TOTAL:
3807         case VDEF_AVERAGE: {
3808             int cnt=0;
3809             double sum=0.0;
3810             for (step=0;step<steps;step++) {
3811                 if (finite(data[step*src->ds_cnt])) {
3812                     sum += data[step*src->ds_cnt];
3813                     cnt ++;
3814                 };
3815             }
3816             if (cnt) {
3817                 if (dst->vf.op == VDEF_TOTAL) {
3818                     dst->vf.val  = sum*src->step;
3819                     dst->vf.when = 0;   /* no time component */
3820                 } else {
3821                     dst->vf.val = sum/cnt;
3822                     dst->vf.when = 0;   /* no time component */
3823                 };
3824             } else {
3825                 dst->vf.val  = DNAN;
3826                 dst->vf.when = 0;
3827             }
3828             }
3829             break;
3830         case VDEF_MINIMUM:
3831             step=0;
3832             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3833             if (step == steps) {
3834                 dst->vf.val  = DNAN;
3835                 dst->vf.when = 0;
3836             } else {
3837                 dst->vf.val  = data[step*src->ds_cnt];
3838                 dst->vf.when = src->start + (step+1)*src->step;
3839             }
3840             while (step != steps) {
3841                 if (finite(data[step*src->ds_cnt])) {
3842                     if (data[step*src->ds_cnt] < dst->vf.val) {
3843                         dst->vf.val  = data[step*src->ds_cnt];
3844                         dst->vf.when = src->start + (step+1)*src->step;
3845                     }
3846                 }
3847                 step++;
3848             }
3849             break;
3850         case VDEF_FIRST:
3851             /* The time value returned here is one step before the
3852              * actual time value.  This is the start of the first
3853              * non-NaN interval.
3854              */
3855             step=0;
3856             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3857             if (step == steps) { /* all entries were NaN */
3858                 dst->vf.val  = DNAN;
3859                 dst->vf.when = 0;
3860             } else {
3861                 dst->vf.val  = data[step*src->ds_cnt];
3862                 dst->vf.when = src->start + step*src->step;
3863             }
3864             break;
3865         case VDEF_LAST:
3866             /* The time value returned here is the
3867              * actual time value.  This is the end of the last
3868              * non-NaN interval.
3869              */
3870             step=steps-1;
3871             while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3872             if (step < 0) { /* all entries were NaN */
3873                 dst->vf.val  = DNAN;
3874                 dst->vf.when = 0;
3875             } else {
3876                 dst->vf.val  = data[step*src->ds_cnt];
3877                 dst->vf.when = src->start + (step+1)*src->step;
3878             }
3879             break;
3880         case VDEF_LSLSLOPE:
3881         case VDEF_LSLINT:
3882         case VDEF_LSLCORREL:{
3883             /* Bestfit line by linear least squares method */ 
3884
3885             int cnt=0;
3886             double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl ;
3887             SUMx = 0; SUMy = 0; SUMxy = 0; SUMxx = 0; SUMyy = 0;
3888
3889             for (step=0;step<steps;step++) {
3890                 if (finite(data[step*src->ds_cnt])) {
3891                     cnt++;
3892                     SUMx  += step;
3893                     SUMxx += step * step;
3894                     SUMxy += step * data[step*src->ds_cnt];
3895                     SUMy  += data[step*src->ds_cnt];
3896                     SUMyy  += data[step*src->ds_cnt]*data[step*src->ds_cnt];
3897                 };
3898             }
3899
3900             slope = ( SUMx*SUMy - cnt*SUMxy ) / ( SUMx*SUMx - cnt*SUMxx );
3901             y_intercept = ( SUMy - slope*SUMx ) / cnt;
3902             correl = (SUMxy - (SUMx*SUMy)/cnt) / sqrt((SUMxx - (SUMx*SUMx)/cnt)*(SUMyy - (SUMy*SUMy)/cnt));
3903
3904             if (cnt) {
3905                     if (dst->vf.op == VDEF_LSLSLOPE) {
3906                         dst->vf.val  = slope;
3907                         dst->vf.when = 0;
3908                     } else if (dst->vf.op == VDEF_LSLINT)  {
3909                         dst->vf.val = y_intercept;
3910                         dst->vf.when = 0;
3911                     } else if (dst->vf.op == VDEF_LSLCORREL)  {
3912                         dst->vf.val = correl;
3913                         dst->vf.when = 0;
3914                     };
3915                 
3916             } else {
3917                 dst->vf.val  = DNAN;
3918                 dst->vf.when = 0;
3919             }
3920         }
3921         break;
3922     }
3923     return 0;
3924 }
3925
3926 /* NaN < -INF < finite_values < INF */
3927 int
3928 vdef_percent_compar(a,b)
3929 const void *a,*b;
3930 {
3931     /* Equality is not returned; this doesn't hurt except
3932      * (maybe) for a little performance.
3933      */
3934
3935     /* First catch NaN values. They are smallest */
3936     if (isnan( *(double *)a )) return -1;
3937     if (isnan( *(double *)b )) return  1;
3938
3939     /* NaN doesn't reach this part so INF and -INF are extremes.
3940      * The sign from isinf() is compatible with the sign we return
3941      */
3942     if (isinf( *(double *)a )) return isinf( *(double *)a );
3943     if (isinf( *(double *)b )) return isinf( *(double *)b );
3944
3945     /* If we reach this, both values must be finite */
3946     if ( *(double *)a < *(double *)b ) return -1; else return 1;
3947 }