Code

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