Code

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