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