Code

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