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