Code

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