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