Evenly-spaced y-axis gridlines.
[rrdtool.git] / src / rrd_graph.c
1 /****************************************************************************
2  * RRDtool 1.1.x  Copyright Tobias Oetiker, 1997 - 2002
3  ****************************************************************************
4  * rrd__graph.c  make creates ne rrds
5  ****************************************************************************/
6
7 #if 0
8 #include "rrd_tool.h"
9 #endif
10
11 #include <sys/stat.h>
12 #ifdef WIN32
13 #include <io.h>
14 #include <fcntl.h>
15 #endif
16 #ifdef HAVE_TIME_H
17 #include <time.h>
18 #endif
19 #ifdef HAVE_LOCALE_H
20 #include <locale.h>
21 #endif
22
23 #include "rrd_graph.h"
24 #include "rrd_graph_helper.h"
25
26 /* some constant definitions */
27
28
29 #ifndef RRD_DEFAULT_FONT
30 #ifdef WIN32
31 #define RRD_DEFAULT_FONT "c:/winnt/fonts/COUR.TTF"
32 #else
33 #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf" 
34 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
35 #endif
36 #endif
37
38
39 text_prop_t text_prop[] = {   
40      { 10.0, RRD_DEFAULT_FONT }, /* default */
41      { 12.0, RRD_DEFAULT_FONT }, /* title */
42      { 8.0,  RRD_DEFAULT_FONT },  /* axis */
43      { 10.0, RRD_DEFAULT_FONT },  /* unit */
44      { 10.0, RRD_DEFAULT_FONT }  /* legend */
45 };
46
47 xlab_t xlab[] = {
48     {0,        TMT_SECOND,30, TMT_MINUTE,5,  TMT_MINUTE,5,         0,"%H:%M"},
49     {2,        TMT_MINUTE,1,  TMT_MINUTE,5,  TMT_MINUTE,5,         0,"%H:%M"},
50     {5,        TMT_MINUTE,2,  TMT_MINUTE,10, TMT_MINUTE,10,        0,"%H:%M"},
51     {10,       TMT_MINUTE,5,  TMT_MINUTE,20, TMT_MINUTE,20,        0,"%H:%M"},
52     {30,       TMT_MINUTE,10, TMT_HOUR,1,    TMT_HOUR,1,           0,"%H:%M"},
53     {60,       TMT_MINUTE,30, TMT_HOUR,2,    TMT_HOUR,2,           0,"%H:%M"},
54     {180,      TMT_HOUR,1,    TMT_HOUR,6,    TMT_HOUR,6,           0,"%H:%M"},
55     /*{300,      TMT_HOUR,3,    TMT_HOUR,12,   TMT_HOUR,12,    12*3600,"%a %p"},  this looks silly*/
56     {600,      TMT_HOUR,6,    TMT_DAY,1,     TMT_DAY,1,      24*3600,"%a"},
57     {1800,     TMT_HOUR,12,   TMT_DAY,1,     TMT_DAY,2,      24*3600,"%a"},
58     {3600,     TMT_DAY,1,     TMT_WEEK,1,     TMT_WEEK,1,    7*24*3600,"Week %V"},
59     {3*3600,   TMT_WEEK,1,      TMT_MONTH,1,     TMT_WEEK,2,    7*24*3600,"Week %V"},
60     {6*3600,   TMT_MONTH,1,   TMT_MONTH,1,   TMT_MONTH,1, 30*24*3600,"%b"},
61     {48*3600,  TMT_MONTH,1,   TMT_MONTH,3,   TMT_MONTH,3, 30*24*3600,"%b"},
62     {10*24*3600, TMT_YEAR,1,  TMT_YEAR,1,    TMT_YEAR,1, 365*24*3600,"%y"},
63     {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
64 };
65
66 /* sensible logarithmic y label intervals ...
67    the first element of each row defines the possible starting points on the
68    y axis ... the other specify the */
69
70 double yloglab[][12]= {{ 1e9, 1,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0 },
71                        {  1e3, 1,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0 },
72                        {  1e1, 1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
73                        /* {  1e1, 1,  5,  0,  0,  0,  0,  0,  0,  0,  0,  0 }, */
74                        {  1e1, 1,  2.5,  5,  7.5,  0,  0,  0,  0,  0,  0,  0 },
75                        {  1e1, 1,  2,  4,  6,  8,  0,  0,  0,  0,  0,  0 },
76                        {  1e1, 1,  2,  3,  4,  5,  6,  7,  8,  9,  0,  0 },
77                        {  0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 }};
78
79 /* sensible y label intervals ...*/
80
81 ylab_t ylab[]= {
82     {0.1, {1,2, 5,10}},
83     {0.2, {1,5,10,20}},
84     {0.5, {1,2, 4,10}},
85     {1.0,   {1,2, 5,10}},
86     {2.0,   {1,5,10,20}},
87     {5.0,   {1,2, 4,10}},
88     {10.0,  {1,2, 5,10}},
89     {20.0,  {1,5,10,20}},
90     {50.0,  {1,2, 4,10}},
91     {100.0, {1,2, 5,10}},
92     {200.0, {1,5,10,20}},
93     {500.0, {1,2, 4,10}},
94     {0.0,   {0,0,0,0}}};
95
96
97 gfx_color_t graph_col[] =   /* default colors */
98 {    0xFFFFFFFF,   /* canvas     */
99      0xF0F0F0FF,   /* background */
100      0xD0D0D0FF,   /* shade A    */
101      0xA0A0A0FF,   /* shade B    */
102      0x909090FF,   /* grid       */
103      0xE05050FF,   /* major grid */
104      0x000000FF,   /* font       */ 
105      0x000000FF,   /* frame      */
106      0xFF0000FF  /* arrow      */
107 };
108
109
110 /* #define DEBUG */
111
112 #ifdef DEBUG
113 # define DPRINT(x)    (void)(printf x, printf("\n"))
114 #else
115 # define DPRINT(x)
116 #endif
117
118
119 /* initialize with xtr(im,0); */
120 int
121 xtr(image_desc_t *im,time_t mytime){
122     static double pixie;
123     if (mytime==0){
124         pixie = (double) im->xsize / (double)(im->end - im->start);
125         return im->xorigin;
126     }
127     return (int)((double)im->xorigin 
128                  + pixie * ( mytime - im->start ) );
129 }
130
131 /* translate data values into y coordinates */
132 double
133 ytr(image_desc_t *im, double value){
134     static double pixie;
135     double yval;
136     if (isnan(value)){
137       if(!im->logarithmic)
138         pixie = (double) im->ysize / (im->maxval - im->minval);
139       else 
140         pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
141       yval = im->yorigin;
142     } else if(!im->logarithmic) {
143       yval = im->yorigin - pixie * (value - im->minval);
144     } else {
145       if (value < im->minval) {
146         yval = im->yorigin;
147       } else {
148         yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
149       }
150     }
151     /* make sure we don't return anything too unreasonable. GD lib can
152        get terribly slow when drawing lines outside its scope. This is 
153        especially problematic in connection with the rigid option */
154     if (! im->rigid) {
155       /* keep yval as-is */
156     } else if (yval > im->yorigin) {
157       yval = im->yorigin+2;
158     } else if (yval < im->yorigin - im->ysize){
159       yval = im->yorigin - im->ysize - 2;
160     } 
161     return yval;
162 }
163
164
165
166 /* conversion function for symbolic entry names */
167
168
169 #define conv_if(VV,VVV) \
170    if (strcmp(#VV, string) == 0) return VVV ;
171
172 enum gf_en gf_conv(char *string){
173     
174     conv_if(PRINT,GF_PRINT)
175     conv_if(GPRINT,GF_GPRINT)
176     conv_if(COMMENT,GF_COMMENT)
177     conv_if(HRULE,GF_HRULE)
178     conv_if(VRULE,GF_VRULE)
179     conv_if(LINE,GF_LINE)
180     conv_if(AREA,GF_AREA)
181     conv_if(STACK,GF_STACK)
182     conv_if(TICK,GF_TICK)
183     conv_if(DEF,GF_DEF)
184     conv_if(CDEF,GF_CDEF)
185     conv_if(VDEF,GF_VDEF)
186     conv_if(PART,GF_PART)
187     
188     return (-1);
189 }
190
191 enum gfx_if_en if_conv(char *string){
192     
193     conv_if(PNG,IF_PNG)
194     conv_if(SVG,IF_SVG)
195     conv_if(EPS,IF_EPS)
196
197     return (-1);
198 }
199
200 enum tmt_en tmt_conv(char *string){
201
202     conv_if(SECOND,TMT_SECOND)
203     conv_if(MINUTE,TMT_MINUTE)
204     conv_if(HOUR,TMT_HOUR)
205     conv_if(DAY,TMT_DAY)
206     conv_if(WEEK,TMT_WEEK)
207     conv_if(MONTH,TMT_MONTH)
208     conv_if(YEAR,TMT_YEAR)
209     return (-1);
210 }
211
212 enum grc_en grc_conv(char *string){
213
214     conv_if(BACK,GRC_BACK)
215     conv_if(CANVAS,GRC_CANVAS)
216     conv_if(SHADEA,GRC_SHADEA)
217     conv_if(SHADEB,GRC_SHADEB)
218     conv_if(GRID,GRC_GRID)
219     conv_if(MGRID,GRC_MGRID)
220     conv_if(FONT,GRC_FONT)
221     conv_if(FRAME,GRC_FRAME)
222     conv_if(ARROW,GRC_ARROW)
223
224     return -1;  
225 }
226
227 enum text_prop_en text_prop_conv(char *string){
228       
229     conv_if(DEFAULT,TEXT_PROP_DEFAULT)
230     conv_if(TITLE,TEXT_PROP_TITLE)
231     conv_if(AXIS,TEXT_PROP_AXIS)
232     conv_if(UNIT,TEXT_PROP_UNIT)
233     conv_if(LEGEND,TEXT_PROP_LEGEND)
234     return -1;
235 }
236
237
238 #undef conv_if
239
240
241
242 int
243 im_free(image_desc_t *im)
244 {
245     long i,ii;
246     if (im == NULL) return 0;
247     for(i=0;i<im->gdes_c;i++){
248       if (im->gdes[i].data_first){
249         /* careful here, because a single pointer can occur several times */
250           free (im->gdes[i].data);
251           if (im->gdes[i].ds_namv){
252               for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
253                   free(im->gdes[i].ds_namv[ii]);
254               free(im->gdes[i].ds_namv);
255           }
256       }
257       free (im->gdes[i].p_data);
258       free (im->gdes[i].rpnp);
259     }
260     free(im->gdes);
261     gfx_destroy(im->canvas);
262     return 0;
263 }
264
265 /* find SI magnitude symbol for the given number*/
266 void
267 auto_scale(
268            image_desc_t *im,   /* image description */
269            double *value,
270            char **symb_ptr,
271            double *magfact
272            )
273 {
274         
275     char *symbol[] = {"a", /* 10e-18 Atto */
276                       "f", /* 10e-15 Femto */
277                       "p", /* 10e-12 Pico */
278                       "n", /* 10e-9  Nano */
279                       "u", /* 10e-6  Micro */
280                       "m", /* 10e-3  Milli */
281                       " ", /* Base */
282                       "k", /* 10e3   Kilo */
283                       "M", /* 10e6   Mega */
284                       "G", /* 10e9   Giga */
285                       "T", /* 10e12  Tera */
286                       "P", /* 10e15  Peta */
287                       "E"};/* 10e18  Exa */
288
289     int symbcenter = 6;
290     int sindex;  
291
292     if (*value == 0.0 || isnan(*value) ) {
293         sindex = 0;
294         *magfact = 1.0;
295     } else {
296         sindex = floor(log(fabs(*value))/log((double)im->base)); 
297         *magfact = pow((double)im->base, (double)sindex);
298         (*value) /= (*magfact);
299     }
300     if ( sindex <= symbcenter && sindex >= -symbcenter) {
301         (*symb_ptr) = symbol[sindex+symbcenter];
302     }
303     else {
304         (*symb_ptr) = "?";
305     }
306 }
307
308
309 /* find SI magnitude symbol for the numbers on the y-axis*/
310 void 
311 si_unit(
312     image_desc_t *im   /* image description */
313 )
314 {
315
316     char symbol[] = {'a', /* 10e-18 Atto */ 
317                      'f', /* 10e-15 Femto */
318                      'p', /* 10e-12 Pico */
319                      'n', /* 10e-9  Nano */
320                      'u', /* 10e-6  Micro */
321                      'm', /* 10e-3  Milli */
322                      ' ', /* Base */
323                      'k', /* 10e3   Kilo */
324                      'M', /* 10e6   Mega */
325                      'G', /* 10e9   Giga */
326                      'T', /* 10e12  Tera */
327                      'P', /* 10e15  Peta */
328                      'E'};/* 10e18  Exa */
329
330     int   symbcenter = 6;
331     double digits;  
332     
333     if (im->unitsexponent != 9999) {
334         /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
335         digits = floor(im->unitsexponent / 3);
336     } else {
337         digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base)); 
338     }
339     im->magfact = pow((double)im->base , digits);
340
341 #ifdef DEBUG
342     printf("digits %6.3f  im->magfact %6.3f\n",digits,im->magfact);
343 #endif
344
345     if ( ((digits+symbcenter) < sizeof(symbol)) &&
346                     ((digits+symbcenter) >= 0) )
347         im->symbol = symbol[(int)digits+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)))) - 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;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     /* pull the data from the log files ... */
683     for (i=0;i<im->gdes_c;i++){
684         /* only GF_DEF elements fetch data */
685         if (im->gdes[i].gf != GF_DEF) 
686             continue;
687
688         skip=0;
689         /* do we have it already ?*/
690         for (ii=0;ii<i;ii++){
691             if (im->gdes[ii].gf != GF_DEF) 
692                 continue;
693             if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
694                 && (im->gdes[i].cf == im->gdes[ii].cf)){
695                 /* OK the data it is here already ... 
696                  * we just copy the header portion */
697                 im->gdes[i].start = im->gdes[ii].start;
698                 im->gdes[i].end = im->gdes[ii].end;
699                 im->gdes[i].step = im->gdes[ii].step;
700                 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
701                 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;             
702                 im->gdes[i].data = im->gdes[ii].data;
703                 im->gdes[i].data_first = 0;
704                 skip=1;
705             }
706             if (skip) 
707                 break;
708         }
709         if (! skip) {
710             unsigned long  ft_step = im->gdes[i].step ;
711             
712             if((rrd_fetch_fn(im->gdes[i].rrd,
713                              im->gdes[i].cf,
714                              &im->gdes[i].start,
715                              &im->gdes[i].end,
716                              &ft_step,
717                              &im->gdes[i].ds_cnt,
718                              &im->gdes[i].ds_namv,
719                              &im->gdes[i].data)) == -1){                
720                 return -1;
721             }
722             im->gdes[i].data_first = 1;     
723         
724             if (ft_step < im->gdes[i].step) {
725                 reduce_data(im->gdes[i].cf,
726                             ft_step,
727                             &im->gdes[i].start,
728                             &im->gdes[i].end,
729                             &im->gdes[i].step,
730                             &im->gdes[i].ds_cnt,
731                             &im->gdes[i].data);
732             } else {
733                 im->gdes[i].step = ft_step;
734             }
735         }
736         
737         /* lets see if the required data source is realy there */
738         for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
739             if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
740                 im->gdes[i].ds=ii; }
741         }
742         if (im->gdes[i].ds== -1){
743             rrd_set_error("No DS called '%s' in '%s'",
744                           im->gdes[i].ds_nam,im->gdes[i].rrd);
745             return -1; 
746         }
747         
748     }
749     return 0;
750 }
751
752 /* evaluate the expressions in the CDEF functions */
753
754 /*************************************************************
755  * CDEF stuff 
756  *************************************************************/
757
758 long
759 find_var_wrapper(void *arg1, char *key)
760 {
761    return find_var((image_desc_t *) arg1, key);
762 }
763
764 /* find gdes containing var*/
765 long
766 find_var(image_desc_t *im, char *key){
767     long ii;
768     for(ii=0;ii<im->gdes_c-1;ii++){
769         if((im->gdes[ii].gf == GF_DEF 
770             || im->gdes[ii].gf == GF_VDEF
771             || im->gdes[ii].gf == GF_CDEF) 
772            && (strcmp(im->gdes[ii].vname,key) == 0)){
773             return ii; 
774         }          
775     }               
776     return -1;
777 }
778
779 /* find the largest common denominator for all the numbers
780    in the 0 terminated num array */
781 long
782 lcd(long *num){
783     long rest;
784     int i;
785     for (i=0;num[i+1]!=0;i++){
786         do { 
787             rest=num[i] % num[i+1];
788             num[i]=num[i+1]; num[i+1]=rest;
789         } while (rest!=0);
790         num[i+1] = num[i];
791     }
792 /*    return i==0?num[i]:num[i-1]; */
793       return num[i];
794 }
795
796 /* run the rpn calculator on all the VDEF and CDEF arguments */
797 int
798 data_calc( image_desc_t *im){
799
800     int       gdi;
801     int       dataidx;
802     long      *steparray, rpi;
803     int       stepcnt;
804     time_t    now;
805     rpnstack_t rpnstack;
806
807     rpnstack_init(&rpnstack);
808
809     for (gdi=0;gdi<im->gdes_c;gdi++){
810         /* Look for GF_VDEF and GF_CDEF in the same loop,
811          * so CDEFs can use VDEFs and vice versa
812          */
813         switch (im->gdes[gdi].gf) {
814             case GF_VDEF:
815                 /* A VDEF has no DS.  This also signals other parts
816                  * of rrdtool that this is a VDEF value, not a CDEF.
817                  */
818                 im->gdes[gdi].ds_cnt = 0;
819                 if (vdef_calc(im,gdi)) {
820                     rrd_set_error("Error processing VDEF '%s'"
821                         ,im->gdes[gdi].vname
822                         );
823                     rpnstack_free(&rpnstack);
824                     return -1;
825                 }
826                 break;
827             case GF_CDEF:
828                 im->gdes[gdi].ds_cnt = 1;
829                 im->gdes[gdi].ds = 0;
830                 im->gdes[gdi].data_first = 1;
831                 im->gdes[gdi].start = 0;
832                 im->gdes[gdi].end = 0;
833                 steparray=NULL;
834                 stepcnt = 0;
835                 dataidx=-1;
836
837                 /* Find the variables in the expression.
838                  * - VDEF variables are substituted by their values
839                  *   and the opcode is changed into OP_NUMBER.
840                  * - CDEF variables are analized for their step size,
841                  *   the lowest common denominator of all the step
842                  *   sizes of the data sources involved is calculated
843                  *   and the resulting number is the step size for the
844                  *   resulting data source.
845                  */
846                 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
847                     if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
848                         long ptr = im->gdes[gdi].rpnp[rpi].ptr;
849                         if (im->gdes[ptr].ds_cnt == 0) {
850 #if 0
851 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
852         im->gdes[gdi].vname,
853         im->gdes[ptr].vname);
854 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
855 #endif
856                             im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
857                             im->gdes[gdi].rpnp[rpi].op  = OP_NUMBER;
858                         } else {
859                             if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
860                                 rrd_set_error("realloc steparray");
861                                 rpnstack_free(&rpnstack);
862                                 return -1;
863                             };
864
865                             steparray[stepcnt-1] = im->gdes[ptr].step;
866
867                             /* adjust start and end of cdef (gdi) so
868                              * that it runs from the latest start point
869                              * to the earliest endpoint of any of the
870                              * rras involved (ptr)
871                              */
872                             if(im->gdes[gdi].start < im->gdes[ptr].start)
873                                 im->gdes[gdi].start = im->gdes[ptr].start;
874
875                             if(im->gdes[gdi].end == 0 ||
876                                         im->gdes[gdi].end > im->gdes[ptr].end)
877                                 im->gdes[gdi].end = im->gdes[ptr].end;
878                 
879                             /* store pointer to the first element of
880                              * the rra providing data for variable,
881                              * further save step size and data source
882                              * count of this rra
883                              */ 
884                             im->gdes[gdi].rpnp[rpi].data =  im->gdes[ptr].data + im->gdes[ptr].ds;
885                             im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
886                             im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
887
888                             /* backoff the *.data ptr; this is done so
889                              * rpncalc() function doesn't have to treat
890                              * the first case differently
891                              */
892                         } /* if ds_cnt != 0 */
893                     } /* if OP_VARIABLE */
894                 } /* loop through all rpi */
895
896                 /* move the data pointers to the correct period */
897                 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
898                     if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
899                         long ptr = im->gdes[gdi].rpnp[rpi].ptr;
900                         if(im->gdes[gdi].start > im->gdes[ptr].start) {
901                             im->gdes[gdi].rpnp[rpi].data += im->gdes[gdi].rpnp[rpi].ds_cnt;
902                         }
903                      }
904                 }
905         
906
907                 if(steparray == NULL){
908                     rrd_set_error("rpn expressions without DEF"
909                                 " or CDEF variables are not supported");
910                     rpnstack_free(&rpnstack);
911                     return -1;    
912                 }
913                 steparray[stepcnt]=0;
914                 /* Now find the resulting step.  All steps in all
915                  * used RRAs have to be visited
916                  */
917                 im->gdes[gdi].step = lcd(steparray);
918                 free(steparray);
919                 if((im->gdes[gdi].data = malloc((
920                                 (im->gdes[gdi].end-im->gdes[gdi].start) 
921                                     / im->gdes[gdi].step)
922                                     * sizeof(double)))==NULL){
923                     rrd_set_error("malloc im->gdes[gdi].data");
924                     rpnstack_free(&rpnstack);
925                     return -1;
926                 }
927         
928                 /* Step through the new cdef results array and
929                  * calculate the values
930                  */
931                 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
932                                 now<=im->gdes[gdi].end;
933                                 now += im->gdes[gdi].step)
934                 {
935                     rpnp_t  *rpnp = im -> gdes[gdi].rpnp;
936
937                     /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
938                      * in this case we are advancing by timesteps;
939                      * we use the fact that time_t is a synonym for long
940                      */
941                     if (rpn_calc(rpnp,&rpnstack,(long) now, 
942                                 im->gdes[gdi].data,++dataidx) == -1) {
943                         /* rpn_calc sets the error string */
944                         rpnstack_free(&rpnstack); 
945                         return -1;
946                     } 
947                 } /* enumerate over time steps within a CDEF */
948                 break;
949             default:
950                 continue;
951         }
952     } /* enumerate over CDEFs */
953     rpnstack_free(&rpnstack);
954     return 0;
955 }
956
957 /* massage data so, that we get one value for each x coordinate in the graph */
958 int
959 data_proc( image_desc_t *im ){
960     long i,ii;
961     double pixstep = (double)(im->end-im->start)
962         /(double)im->xsize; /* how much time 
963                                passes in one pixel */
964     double paintval;
965     double minval=DNAN,maxval=DNAN;
966     
967     unsigned long gr_time;    
968
969     /* memory for the processed data */
970     for(i=0;i<im->gdes_c;i++){
971       if((im->gdes[i].gf==GF_LINE) ||
972          (im->gdes[i].gf==GF_AREA) ||
973          (im->gdes[i].gf==GF_TICK) ||
974          (im->gdes[i].gf==GF_STACK)){
975         if((im->gdes[i].p_data = malloc((im->xsize +1)
976                                         * sizeof(rrd_value_t)))==NULL){
977           rrd_set_error("malloc data_proc");
978           return -1;
979         }
980       }
981     }
982     
983     for(i=0;i<im->xsize;i++){
984         long vidx;
985         gr_time = im->start+pixstep*i; /* time of the 
986                                           current step */
987         paintval=0.0;
988         
989         for(ii=0;ii<im->gdes_c;ii++){
990           double value;
991             switch(im->gdes[ii].gf){
992             case GF_LINE:
993             case GF_AREA:
994                 case GF_TICK:
995                 paintval = 0.0;
996             case GF_STACK:
997                 vidx = im->gdes[ii].vidx;
998
999                 value =
1000                     im->gdes[vidx].data[
1001                         ((unsigned long)floor(
1002         (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
1003                                              )
1004                         )       *im->gdes[vidx].ds_cnt
1005                                 +im->gdes[vidx].ds];
1006
1007                 if (! isnan(value)) {
1008                   paintval += value;
1009                   im->gdes[ii].p_data[i] = paintval;
1010                   /* GF_TICK: the data values are not relevant for min and max */
1011                   if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
1012                    if (isnan(minval) || paintval <  minval)
1013                      minval = paintval;
1014                    if (isnan(maxval) || paintval >  maxval)
1015                      maxval = paintval;
1016                   }
1017                 } else {
1018                   im->gdes[ii].p_data[i] = DNAN;
1019                 }
1020                 break;
1021             case GF_PRINT:
1022             case GF_GPRINT:
1023             case GF_COMMENT:
1024             case GF_HRULE:
1025             case GF_VRULE:
1026             case GF_DEF:               
1027             case GF_CDEF:
1028             case GF_VDEF:
1029             case GF_PART:
1030                 break;
1031             }
1032         }
1033     }
1034
1035     /* if min or max have not been asigned a value this is because
1036        there was no data in the graph ... this is not good ...
1037        lets set these to dummy values then ... */
1038
1039     if (isnan(minval)) minval = 0.0;
1040     if (isnan(maxval)) maxval = 1.0;
1041     
1042     /* adjust min and max values */
1043     if (isnan(im->minval) 
1044         || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
1045             && im->minval > minval))
1046         im->minval = minval;
1047     if (isnan(im->maxval) 
1048         || (!im->rigid 
1049             && im->maxval < maxval)){
1050         if (im->logarithmic)
1051             im->maxval = maxval * 1.1;
1052         else
1053             im->maxval = maxval;
1054     }
1055     /* make sure min and max are not equal */
1056     if (im->minval == im->maxval) {
1057       im->maxval *= 1.01; 
1058       if (! im->logarithmic) {
1059         im->minval *= 0.99;
1060       }
1061       
1062       /* make sure min and max are not both zero */
1063       if (im->maxval == 0.0) {
1064             im->maxval = 1.0;
1065       }
1066         
1067     }
1068     return 0;
1069 }
1070
1071
1072
1073 /* identify the point where the first gridline, label ... gets placed */
1074
1075 time_t
1076 find_first_time(
1077     time_t   start, /* what is the initial time */
1078     enum tmt_en baseint,  /* what is the basic interval */
1079     long     basestep /* how many if these do we jump a time */
1080     )
1081 {
1082     struct tm tm;
1083     tm = *localtime(&start);
1084     switch(baseint){
1085     case TMT_SECOND:
1086         tm.tm_sec -= tm.tm_sec % basestep; break;
1087     case TMT_MINUTE: 
1088         tm.tm_sec=0;
1089         tm.tm_min -= tm.tm_min % basestep; 
1090         break;
1091     case TMT_HOUR:
1092         tm.tm_sec=0;
1093         tm.tm_min = 0;
1094         tm.tm_hour -= tm.tm_hour % basestep; break;
1095     case TMT_DAY:
1096         /* we do NOT look at the basestep for this ... */
1097         tm.tm_sec=0;
1098         tm.tm_min = 0;
1099         tm.tm_hour = 0; break;
1100     case TMT_WEEK:
1101         /* we do NOT look at the basestep for this ... */
1102         tm.tm_sec=0;
1103         tm.tm_min = 0;
1104         tm.tm_hour = 0;
1105         tm.tm_mday -= tm.tm_wday -1;    /* -1 because we want the monday */
1106         if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1107         break;
1108     case TMT_MONTH:
1109         tm.tm_sec=0;
1110         tm.tm_min = 0;
1111         tm.tm_hour = 0;
1112         tm.tm_mday = 1;
1113         tm.tm_mon -= tm.tm_mon % basestep; break;
1114
1115     case TMT_YEAR:
1116         tm.tm_sec=0;
1117         tm.tm_min = 0;
1118         tm.tm_hour = 0;
1119         tm.tm_mday = 1;
1120         tm.tm_mon = 0;
1121         tm.tm_year -= (tm.tm_year+1900) % basestep;
1122         
1123     }
1124     return mktime(&tm);
1125 }
1126 /* identify the point where the next gridline, label ... gets placed */
1127 time_t 
1128 find_next_time(
1129     time_t   current, /* what is the initial time */
1130     enum tmt_en baseint,  /* what is the basic interval */
1131     long     basestep /* how many if these do we jump a time */
1132     )
1133 {
1134     struct tm tm;
1135     time_t madetime;
1136     tm = *localtime(&current);
1137     do {
1138         switch(baseint){
1139         case TMT_SECOND:
1140             tm.tm_sec += basestep; break;
1141         case TMT_MINUTE: 
1142             tm.tm_min += basestep; break;
1143         case TMT_HOUR:
1144             tm.tm_hour += basestep; break;
1145         case TMT_DAY:
1146             tm.tm_mday += basestep; break;
1147         case TMT_WEEK:
1148             tm.tm_mday += 7*basestep; break;
1149         case TMT_MONTH:
1150             tm.tm_mon += basestep; break;
1151         case TMT_YEAR:
1152             tm.tm_year += basestep;     
1153         }
1154         madetime = mktime(&tm);
1155     } while (madetime == -1); /* this is necessary to skip impssible times
1156                                  like the daylight saving time skips */
1157     return madetime;
1158           
1159 }
1160
1161
1162 /* calculate values required for PRINT and GPRINT functions */
1163
1164 int
1165 print_calc(image_desc_t *im, char ***prdata) 
1166 {
1167     long i,ii,validsteps;
1168     double printval;
1169     time_t printtime;
1170     int graphelement = 0;
1171     long vidx;
1172     int max_ii; 
1173     double magfact = -1;
1174     char *si_symb = "";
1175     char *percent_s;
1176     int prlines = 1;
1177     if (im->imginfo) prlines++;
1178     for(i=0;i<im->gdes_c;i++){
1179         switch(im->gdes[i].gf){
1180         case GF_PRINT:
1181             prlines++;
1182             if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1183                 rrd_set_error("realloc prdata");
1184                 return 0;
1185             }
1186         case GF_GPRINT:
1187             /* PRINT and GPRINT can now print VDEF generated values.
1188              * There's no need to do any calculations on them as these
1189              * calculations were already made.
1190              */
1191             vidx = im->gdes[i].vidx;
1192             if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1193                 printval = im->gdes[vidx].vf.val;
1194                 printtime = im->gdes[vidx].vf.when;
1195             } else { /* need to calculate max,min,avg etcetera */
1196                 max_ii =((im->gdes[vidx].end 
1197                         - im->gdes[vidx].start)
1198                         / im->gdes[vidx].step
1199                         * im->gdes[vidx].ds_cnt);
1200                 printval = DNAN;
1201                 validsteps = 0;
1202                 for(    ii=im->gdes[vidx].ds;
1203                         ii < max_ii;
1204                         ii+=im->gdes[vidx].ds_cnt){
1205                     if (! finite(im->gdes[vidx].data[ii]))
1206                         continue;
1207                     if (isnan(printval)){
1208                         printval = im->gdes[vidx].data[ii];
1209                         validsteps++;
1210                         continue;
1211                     }
1212
1213                     switch (im->gdes[i].cf){
1214                         case CF_HWPREDICT:
1215                         case CF_DEVPREDICT:
1216                         case CF_DEVSEASONAL:
1217                         case CF_SEASONAL:
1218                         case CF_AVERAGE:
1219                             validsteps++;
1220                             printval += im->gdes[vidx].data[ii];
1221                             break;
1222                         case CF_MINIMUM:
1223                             printval = min( printval, im->gdes[vidx].data[ii]);
1224                             break;
1225                         case CF_FAILURES:
1226                         case CF_MAXIMUM:
1227                             printval = max( printval, im->gdes[vidx].data[ii]);
1228                             break;
1229                         case CF_LAST:
1230                             printval = im->gdes[vidx].data[ii];
1231                     }
1232                 }
1233                 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1234                     if (validsteps > 1) {
1235                         printval = (printval / validsteps);
1236                     }
1237                 }
1238             } /* prepare printval */
1239
1240             if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1241                 if (im->gdes[i].gf == GF_PRINT){
1242                     (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1243                     sprintf((*prdata)[prlines-2],"%s (%lu)",
1244                                         ctime(&printtime),printtime);
1245                     (*prdata)[prlines-1] = NULL;
1246                 } else {
1247                     sprintf(im->gdes[i].legend,"%s (%lu)",
1248                                         ctime(&printtime),printtime);
1249                     graphelement = 1;
1250                 }
1251             } else {
1252             if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1253                 /* Magfact is set to -1 upon entry to print_calc.  If it
1254                  * is still less than 0, then we need to run auto_scale.
1255                  * Otherwise, put the value into the correct units.  If
1256                  * the value is 0, then do not set the symbol or magnification
1257                  * so next the calculation will be performed again. */
1258                 if (magfact < 0.0) {
1259                     auto_scale(im,&printval,&si_symb,&magfact);
1260                     if (printval == 0.0)
1261                         magfact = -1.0;
1262                 } else {
1263                     printval /= magfact;
1264                 }
1265                 *(++percent_s) = 's';
1266             } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1267                 auto_scale(im,&printval,&si_symb,&magfact);
1268             }
1269
1270             if (im->gdes[i].gf == GF_PRINT){
1271                 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1272                 if (bad_format(im->gdes[i].format)) {
1273                         rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1274                         return -1;
1275                 }
1276 #ifdef HAVE_SNPRINTF
1277                 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1278 #else
1279                 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1280 #endif
1281                 (*prdata)[prlines-1] = NULL;
1282             } else {
1283                 /* GF_GPRINT */
1284
1285                 if (bad_format(im->gdes[i].format)) {
1286                         rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1287                         return -1;
1288                 }
1289 #ifdef HAVE_SNPRINTF
1290                 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1291 #else
1292                 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1293 #endif
1294                 graphelement = 1;
1295             }
1296             }
1297             break;
1298         case GF_LINE:
1299         case GF_AREA:
1300         case GF_TICK:
1301         case GF_STACK:
1302         case GF_HRULE:
1303         case GF_VRULE:
1304             graphelement = 1;
1305             break;
1306         case GF_COMMENT:
1307         case GF_DEF:
1308         case GF_CDEF:       
1309         case GF_VDEF:       
1310         case GF_PART:
1311             break;
1312         }
1313     }
1314     return graphelement;
1315 }
1316
1317
1318 /* place legends with color spots */
1319 int
1320 leg_place(image_desc_t *im)
1321 {
1322     /* graph labels */
1323     int   interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1324     int   box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1325     int   border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1326     int   fill=0, fill_last;
1327     int   leg_c = 0;
1328     int   leg_x = border, leg_y = im->yimg;
1329     int   leg_cc;
1330     int   glue = 0;
1331     int   i,ii, mark = 0;
1332     char  prt_fctn; /*special printfunctions */
1333     int  *legspace;
1334
1335   if( !(im->extra_flags & NOLEGEND) ) {
1336     if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1337        rrd_set_error("malloc for legspace");
1338        return -1;
1339     }
1340
1341     for(i=0;i<im->gdes_c;i++){
1342         fill_last = fill;
1343
1344         leg_cc = strlen(im->gdes[i].legend);
1345         
1346         /* is there a controle code ant the end of the legend string ? */ 
1347         if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1348             prt_fctn = im->gdes[i].legend[leg_cc-1];
1349             leg_cc -= 2;
1350             im->gdes[i].legend[leg_cc] = '\0';
1351         } else {
1352             prt_fctn = '\0';
1353         }
1354         /* remove exess space */
1355         while (prt_fctn=='g' && 
1356                leg_cc > 0 && 
1357                im->gdes[i].legend[leg_cc-1]==' '){
1358            leg_cc--;
1359            im->gdes[i].legend[leg_cc]='\0';
1360         }
1361         if (leg_cc != 0 ){
1362            legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1363            
1364            if (fill > 0){ 
1365                /* no interleg space if string ends in \g */
1366                fill += legspace[i];
1367             }
1368             if (im->gdes[i].gf != GF_GPRINT && 
1369                 im->gdes[i].gf != GF_COMMENT) { 
1370                 fill += box;       
1371             }
1372            fill += gfx_get_text_width(im->canvas, fill+border,
1373                                       im->text_prop[TEXT_PROP_LEGEND].font,
1374                                       im->text_prop[TEXT_PROP_LEGEND].size,
1375                                       im->tabwidth,
1376                                       im->gdes[i].legend);
1377             leg_c++;
1378         } else {
1379            legspace[i]=0;
1380         }
1381         /* who said there was a special tag ... ?*/
1382         if (prt_fctn=='g') {    
1383            prt_fctn = '\0';
1384         }
1385         if (prt_fctn == '\0') {
1386             if (i == im->gdes_c -1 ) prt_fctn ='l';
1387             
1388             /* is it time to place the legends ? */
1389             if (fill > im->ximg - 2*border){
1390                 if (leg_c > 1) {
1391                     /* go back one */
1392                     i--; 
1393                     fill = fill_last;
1394                     leg_c--;
1395                     prt_fctn = 'j';
1396                 } else {
1397                     prt_fctn = 'l';
1398                 }
1399                 
1400             }
1401         }
1402
1403
1404         if (prt_fctn != '\0'){
1405             leg_x = border;
1406             if (leg_c >= 2 && prt_fctn == 'j') {
1407                 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1408             } else {
1409                 glue = 0;
1410             }
1411             if (prt_fctn =='c') leg_x =  (im->ximg - fill) / 2.0;
1412             if (prt_fctn =='r') leg_x =  im->ximg - fill - border;
1413
1414             for(ii=mark;ii<=i;ii++){
1415                 if(im->gdes[ii].legend[0]=='\0')
1416                     continue;
1417                 im->gdes[ii].leg_x = leg_x;
1418                 im->gdes[ii].leg_y = leg_y;
1419                 leg_x += 
1420                  gfx_get_text_width(im->canvas, leg_x,
1421                                       im->text_prop[TEXT_PROP_LEGEND].font,
1422                                       im->text_prop[TEXT_PROP_LEGEND].size,
1423                                       im->tabwidth,
1424                                       im->gdes[ii].legend) 
1425                    + legspace[ii]
1426                    + glue;
1427                 if (im->gdes[ii].gf != GF_GPRINT && 
1428                     im->gdes[ii].gf != GF_COMMENT) 
1429                     leg_x += box;          
1430             }       
1431             leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1432             if (prt_fctn == 's') leg_y -=  im->text_prop[TEXT_PROP_LEGEND].size*1.2;       
1433             fill = 0;
1434             leg_c = 0;
1435             mark = ii;
1436         }          
1437     }
1438     im->yimg = leg_y;
1439     free(legspace);
1440   }
1441   return 0;
1442 }
1443
1444 /* create a grid on the graph. it determines what to do
1445    from the values of xsize, start and end */
1446
1447 /* the xaxis labels are determined from the number of seconds per pixel
1448    in the requested graph */
1449
1450
1451
1452 int
1453 calc_horizontal_grid(image_desc_t   *im)
1454 {
1455     double   range;
1456     double   scaledrange;
1457     int      pixel,i;
1458     int      gridind;
1459     int      decimals, fractionals;
1460
1461     im->ygrid_scale.labfact=2;
1462     gridind=-1;
1463     range =  im->maxval - im->minval;
1464     scaledrange = range / im->magfact;
1465
1466         /* does the scale of this graph make it impossible to put lines
1467            on it? If so, give up. */
1468         if (isnan(scaledrange)) {
1469                 return 0;
1470         }
1471
1472     /* find grid spaceing */
1473     pixel=1;
1474     if(isnan(im->ygridstep)){
1475         if(im->extra_flags & ALTYGRID) {
1476             /* find the value with max number of digits. Get number of digits */
1477             decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1478             if(decimals <= 0) /* everything is small. make place for zero */
1479                 decimals = 1;
1480             
1481             fractionals = floor(log10(range));
1482             if(fractionals < 0) /* small amplitude. */
1483                 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1484             else
1485                 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1486             im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1487             if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1488                 im->ygrid_scale.gridstep = 0.1;
1489             /* should have at least 5 lines but no more then 15 */
1490             if(range/im->ygrid_scale.gridstep < 5)
1491                 im->ygrid_scale.gridstep /= 10;
1492             if(range/im->ygrid_scale.gridstep > 15)
1493                 im->ygrid_scale.gridstep *= 10;
1494             if(range/im->ygrid_scale.gridstep > 5) {
1495                 im->ygrid_scale.labfact = 1;
1496                 if(range/im->ygrid_scale.gridstep > 8)
1497                     im->ygrid_scale.labfact = 2;
1498             }
1499             else {
1500                 im->ygrid_scale.gridstep /= 5;
1501                 im->ygrid_scale.labfact = 5;
1502             }
1503         }
1504         else {
1505             for(i=0;ylab[i].grid > 0;i++){
1506                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1507                 if (gridind == -1 && pixel > 5) {
1508                     gridind = i;
1509                     break;
1510                 }
1511             }
1512             
1513             for(i=0; i<4;i++) {
1514                if (pixel * ylab[gridind].lfac[i] >=  2 * im->text_prop[TEXT_PROP_AXIS].size) {
1515                   im->ygrid_scale.labfact =  ylab[gridind].lfac[i];
1516                   break;
1517                }                          
1518             } 
1519             
1520             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1521         }
1522     } else {
1523         im->ygrid_scale.gridstep = im->ygridstep;
1524         im->ygrid_scale.labfact = im->ylabfact;
1525     }
1526     return 1;
1527 }
1528
1529 int draw_horizontal_grid(image_desc_t *im)
1530 {
1531     int      i;
1532     double   scaledstep;
1533     char     graph_label[100];
1534     double X0=im->xorigin;
1535     double X1=im->xorigin+im->xsize;
1536    
1537     int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1538     int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1539     scaledstep = im->ygrid_scale.gridstep/im->magfact;
1540     for (i = sgrid; i <= egrid; i++){
1541        double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1542        if ( Y0 >= im->yorigin-im->ysize
1543                  && Y0 <= im->yorigin){       
1544             if(i % im->ygrid_scale.labfact == 0){               
1545                 if (i==0 || im->symbol == ' ') {
1546                     if(scaledstep < 1){
1547                         if(im->extra_flags & ALTYGRID) {
1548                             sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1549                         }
1550                         else {
1551                             sprintf(graph_label,"%4.1f",scaledstep*i);
1552                         }
1553                     } else {
1554                         sprintf(graph_label,"%4.0f",scaledstep*i);
1555                     }
1556                 }else {
1557                     if(scaledstep < 1){
1558                         sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1559                     } else {
1560                         sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1561                     }
1562                 }
1563
1564                gfx_new_text ( im->canvas,
1565                               X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1566                               im->graph_col[GRC_FONT],
1567                               im->text_prop[TEXT_PROP_AXIS].font,
1568                               im->text_prop[TEXT_PROP_AXIS].size,
1569                               im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1570                               graph_label );
1571                gfx_new_line ( im->canvas,
1572                               X0-2,Y0,
1573                               X1+2,Y0,
1574                               MGRIDWIDTH, im->graph_col[GRC_MGRID] );          
1575                
1576             } else {            
1577                gfx_new_line ( im->canvas,
1578                               X0-1,Y0,
1579                               X1+1,Y0,
1580                               GRIDWIDTH, im->graph_col[GRC_GRID] );            
1581                
1582             }       
1583         }       
1584     } 
1585     return 1;
1586 }
1587
1588 /* logaritmic horizontal grid */
1589 int
1590 horizontal_log_grid(image_desc_t   *im)   
1591 {
1592     double   pixpex;
1593     int      ii,i;
1594     int      minoridx=0, majoridx=0;
1595     char     graph_label[100];
1596     double   X0,X1,Y0;   
1597     double   value, pixperstep, minstep;
1598
1599     /* find grid spaceing */
1600     pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1601
1602         if (isnan(pixpex)) {
1603                 return 0;
1604         }
1605
1606     for(i=0;yloglab[i][0] > 0;i++){
1607         minstep = log10(yloglab[i][0]);
1608         for(ii=1;yloglab[i][ii+1] > 0;ii++){
1609             if(yloglab[i][ii+2]==0){
1610                 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1611                 break;
1612             }
1613         }
1614         pixperstep = pixpex * minstep;
1615         if(pixperstep > 5){minoridx = i;}
1616        if(pixperstep > 2 *  im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1617     }
1618    
1619    X0=im->xorigin;
1620    X1=im->xorigin+im->xsize;
1621     /* paint minor grid */
1622     for (value = pow((double)10, log10(im->minval) 
1623                           - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1624          value  <= im->maxval;
1625          value *= yloglab[minoridx][0]){
1626         if (value < im->minval) continue;
1627         i=0;    
1628         while(yloglab[minoridx][++i] > 0){          
1629            Y0 = ytr(im,value * yloglab[minoridx][i]);
1630            if (Y0 <= im->yorigin - im->ysize) break;
1631            gfx_new_line ( im->canvas,
1632                           X0-1,Y0,
1633                           X1+1,Y0,
1634                           GRIDWIDTH, im->graph_col[GRC_GRID] );
1635         }
1636     }
1637
1638     /* paint major grid and labels*/
1639     for (value = pow((double)10, log10(im->minval) 
1640                           - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1641          value <= im->maxval;
1642          value *= yloglab[majoridx][0]){
1643         if (value < im->minval) continue;
1644         i=0;    
1645         while(yloglab[majoridx][++i] > 0){          
1646            Y0 = ytr(im,value * yloglab[majoridx][i]);    
1647            if (Y0 <= im->yorigin - im->ysize) break;
1648            gfx_new_line ( im->canvas,
1649                           X0-2,Y0,
1650                           X1+2,Y0,
1651                           MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1652            
1653            sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1654            gfx_new_text ( im->canvas,
1655                           X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1656                           im->graph_col[GRC_FONT],
1657                           im->text_prop[TEXT_PROP_AXIS].font,
1658                           im->text_prop[TEXT_PROP_AXIS].size,
1659                           im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1660                           graph_label );
1661         } 
1662     }
1663         return 1;
1664 }
1665
1666
1667 void
1668 vertical_grid(
1669     image_desc_t   *im )
1670 {   
1671     int xlab_sel;               /* which sort of label and grid ? */
1672     time_t ti, tilab;
1673     long factor;
1674     char graph_label[100];
1675     double X0,Y0,Y1; /* points for filled graph and more*/
1676    
1677
1678     /* the type of time grid is determined by finding
1679        the number of seconds per pixel in the graph */
1680     
1681     
1682     if(im->xlab_user.minsec == -1){
1683         factor=(im->end - im->start)/im->xsize;
1684         xlab_sel=0;
1685         while ( xlab[xlab_sel+1].minsec != -1 
1686                 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1687         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1688         im->xlab_user.gridst = xlab[xlab_sel].gridst;
1689         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1690         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1691         im->xlab_user.labtm = xlab[xlab_sel].labtm;
1692         im->xlab_user.labst = xlab[xlab_sel].labst;
1693         im->xlab_user.precis = xlab[xlab_sel].precis;
1694         im->xlab_user.stst = xlab[xlab_sel].stst;
1695     }
1696     
1697     /* y coords are the same for every line ... */
1698     Y0 = im->yorigin;
1699     Y1 = im->yorigin-im->ysize;
1700    
1701
1702     /* paint the minor grid */
1703     for(ti = find_first_time(im->start,
1704                             im->xlab_user.gridtm,
1705                             im->xlab_user.gridst);
1706         ti < im->end; 
1707         ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1708         ){
1709         /* are we inside the graph ? */
1710         if (ti < im->start || ti > im->end) continue;
1711        X0 = xtr(im,ti);       
1712        gfx_new_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH, im->graph_col[GRC_GRID]);
1713        
1714     }
1715
1716     /* paint the major grid */
1717     for(ti = find_first_time(im->start,
1718                             im->xlab_user.mgridtm,
1719                             im->xlab_user.mgridst);
1720         ti < im->end; 
1721         ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1722         ){
1723         /* are we inside the graph ? */
1724         if (ti < im->start || ti > im->end) continue;
1725        X0 = xtr(im,ti);
1726        gfx_new_line(im->canvas,X0,Y0+2, X0,Y1-2,MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1727        
1728     }
1729     /* paint the labels below the graph */
1730     for(ti = find_first_time(im->start,
1731                             im->xlab_user.labtm,
1732                             im->xlab_user.labst);
1733         ti <= im->end; 
1734         ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1735         ){
1736         tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1737         /* are we inside the graph ? */
1738         if (ti < im->start || ti > im->end) continue;
1739
1740 #if HAVE_STRFTIME
1741         strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1742 #else
1743 # error "your libc has no strftime I guess we'll abort the exercise here."
1744 #endif
1745        gfx_new_text ( im->canvas,
1746                       xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1747                       im->graph_col[GRC_FONT],
1748                       im->text_prop[TEXT_PROP_AXIS].font,
1749                       im->text_prop[TEXT_PROP_AXIS].size,
1750                       im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1751                       graph_label );
1752        
1753     }
1754
1755 }
1756
1757
1758 void 
1759 axis_paint(
1760    image_desc_t   *im
1761            )
1762 {   
1763     /* draw x and y axis */
1764     gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1765                       im->xorigin+im->xsize,im->yorigin-im->ysize,
1766                       GRIDWIDTH, im->graph_col[GRC_GRID]);
1767        
1768        gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1769                          im->xorigin+im->xsize,im->yorigin-im->ysize,
1770                          GRIDWIDTH, im->graph_col[GRC_GRID]);
1771    
1772        gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1773                          im->xorigin+im->xsize+4,im->yorigin,
1774                          MGRIDWIDTH, im->graph_col[GRC_GRID]);
1775    
1776        gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1777                          im->xorigin,im->yorigin-im->ysize-4,
1778                          MGRIDWIDTH, im->graph_col[GRC_GRID]);
1779    
1780     
1781     /* arrow for X axis direction */
1782     gfx_new_area ( im->canvas, 
1783                    im->xorigin+im->xsize+3,  im->yorigin-3,
1784                    im->xorigin+im->xsize+3,  im->yorigin+4,
1785                    im->xorigin+im->xsize+8,  im->yorigin+0.5, /* LINEOFFSET */
1786                    im->graph_col[GRC_ARROW]);
1787    
1788    
1789    
1790 }
1791
1792 void
1793 grid_paint(image_desc_t   *im)
1794 {   
1795     long i;
1796     int res=0;
1797     double X0,Y0; /* points for filled graph and more*/
1798     gfx_node_t *node;
1799
1800     /* draw 3d border */
1801     node = gfx_new_area (im->canvas, 0,im->yimg,
1802                                  2,im->yimg-2,
1803                                  2,2,im->graph_col[GRC_SHADEA]);
1804     gfx_add_point( node , im->ximg - 2, 2 );
1805     gfx_add_point( node , im->ximg, 0 );
1806     gfx_add_point( node , 0,0 );
1807 /*    gfx_add_point( node , 0,im->yimg ); */
1808    
1809     node =  gfx_new_area (im->canvas, 2,im->yimg-2,
1810                                   im->ximg-2,im->yimg-2,
1811                                   im->ximg - 2, 2,
1812                                  im->graph_col[GRC_SHADEB]);
1813     gfx_add_point( node ,   im->ximg,0);
1814     gfx_add_point( node ,   im->ximg,im->yimg);
1815     gfx_add_point( node ,   0,im->yimg);
1816 /*    gfx_add_point( node , 0,im->yimg ); */
1817    
1818    
1819     if (im->draw_x_grid == 1 )
1820       vertical_grid(im);
1821     
1822     if (im->draw_y_grid == 1){
1823         if(im->logarithmic){
1824                 res = horizontal_log_grid(im);
1825         } else {
1826                 res = draw_horizontal_grid(im);
1827         }
1828
1829         /* dont draw horizontal grid if there is no min and max val */
1830         if (! res ) {
1831           char *nodata = "No Data found";
1832            gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1833                         im->graph_col[GRC_FONT],
1834                         im->text_prop[TEXT_PROP_AXIS].font,
1835                         im->text_prop[TEXT_PROP_AXIS].size,
1836                         im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1837                         nodata );          
1838         }
1839     }
1840
1841     /* yaxis description */
1842         if (im->canvas->imgformat != IF_PNG) {
1843             gfx_new_text( im->canvas,
1844                           7, (im->yorigin - im->ysize/2),
1845                           im->graph_col[GRC_FONT],
1846                           im->text_prop[TEXT_PROP_AXIS].font,
1847                           im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1848                           GFX_H_CENTER, GFX_V_CENTER,
1849                           im->ylegend);
1850         } else {
1851             /* horrible hack until we can actually print vertically */
1852             {
1853                 int n;
1854                 int l=strlen(im->ylegend);
1855                 char s[2];
1856                 for (n=0;n<strlen(im->ylegend);n++) {
1857                     s[0]=im->ylegend[n];
1858                     s[1]='\0';
1859                     gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1860                         im->graph_col[GRC_FONT],
1861                         im->text_prop[TEXT_PROP_AXIS].font,
1862                         im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1863                         GFX_H_CENTER, GFX_V_CENTER,
1864                         s);
1865                 }
1866             }
1867         }
1868    
1869     /* graph title */
1870     gfx_new_text( im->canvas,
1871                   im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1872                   im->graph_col[GRC_FONT],
1873                   im->text_prop[TEXT_PROP_TITLE].font,
1874                   im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1875                   GFX_H_CENTER, GFX_V_CENTER,
1876                   im->title);
1877
1878     /* graph labels */
1879     if( !(im->extra_flags & NOLEGEND) ) {
1880       for(i=0;i<im->gdes_c;i++){
1881         if(im->gdes[i].legend[0] =='\0')
1882             continue;
1883          
1884         /* im->gdes[i].leg_y is the bottom of the legend */
1885                 X0 = im->gdes[i].leg_x;
1886                 Y0 = im->gdes[i].leg_y;
1887                 /* Box needed? */
1888                 if (       im->gdes[i].gf != GF_GPRINT
1889                         && im->gdes[i].gf != GF_COMMENT) {
1890                     int boxH, boxV;
1891
1892                     boxH = gfx_get_text_width(im->canvas, 0,
1893                                 im->text_prop[TEXT_PROP_AXIS].font,
1894                                 im->text_prop[TEXT_PROP_AXIS].size,
1895                                 im->tabwidth,"M") * 1.25;
1896                     boxV = boxH;
1897
1898                     node = gfx_new_area(im->canvas,
1899                                 X0,Y0-boxV,
1900                                 X0,Y0,
1901                                 X0+boxH,Y0,
1902                                 im->gdes[i].col);
1903                     gfx_add_point ( node, X0+boxH, Y0-boxV );
1904                     node = gfx_new_line(im->canvas,
1905                                 X0,Y0-boxV, X0,Y0,
1906                                 1,0x000000FF);
1907                     gfx_add_point(node,X0+boxH,Y0);
1908                     gfx_add_point(node,X0+boxH,Y0-boxV);
1909                     gfx_close_path(node);
1910                     X0 += boxH / 1.25 * 2;
1911                 }
1912                 gfx_new_text ( im->canvas, X0, Y0,
1913                                    im->graph_col[GRC_FONT],
1914                                    im->text_prop[TEXT_PROP_AXIS].font,
1915                                    im->text_prop[TEXT_PROP_AXIS].size,
1916                                    im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1917                                    im->gdes[i].legend );
1918               }
1919            }
1920         }
1921
1922
1923 /*****************************************************
1924  * lazy check make sure we rely need to create this graph
1925  *****************************************************/
1926
1927 int lazy_check(image_desc_t *im){
1928     FILE *fd = NULL;
1929         int size = 1;
1930     struct stat  imgstat;
1931     
1932     if (im->lazy == 0) return 0; /* no lazy option */
1933     if (stat(im->graphfile,&imgstat) != 0) 
1934       return 0; /* can't stat */
1935     /* one pixel in the existing graph is more then what we would
1936        change here ... */
1937     if (time(NULL) - imgstat.st_mtime > 
1938         (im->end - im->start) / im->xsize) 
1939       return 0;
1940     if ((fd = fopen(im->graphfile,"rb")) == NULL) 
1941       return 0; /* the file does not exist */
1942     switch (im->canvas->imgformat) {
1943     case IF_PNG:
1944            size = PngSize(fd,&(im->ximg),&(im->yimg));
1945            break;
1946     default:
1947            size = 1;
1948     }
1949     fclose(fd);
1950     return size;
1951 }
1952
1953 void
1954 pie_part(image_desc_t *im, gfx_color_t color,
1955             double PieCenterX, double PieCenterY, double Radius,
1956             double startangle, double endangle)
1957 {
1958     gfx_node_t *node;
1959     double angle;
1960     double step=M_PI/50; /* Number of iterations for the circle;
1961                          ** 10 is definitely too low, more than
1962                          ** 50 seems to be overkill
1963                          */
1964
1965     /* Strange but true: we have to work clockwise or else
1966     ** anti aliasing nor transparency don't work.
1967     **
1968     ** This test is here to make sure we do it right, also
1969     ** this makes the for...next loop more easy to implement.
1970     ** The return will occur if the user enters a negative number
1971     ** (which shouldn't be done according to the specs) or if the
1972     ** programmers do something wrong (which, as we all know, never
1973     ** happens anyway :)
1974     */
1975     if (endangle<startangle) return;
1976
1977     /* Hidden feature: Radius decreases each full circle */
1978     angle=startangle;
1979     while (angle>=2*M_PI) {
1980         angle  -= 2*M_PI;
1981         Radius *= 0.8;
1982     }
1983
1984     node=gfx_new_area(im->canvas,
1985                 PieCenterX+sin(startangle)*Radius,
1986                 PieCenterY-cos(startangle)*Radius,
1987                 PieCenterX,
1988                 PieCenterY,
1989                 PieCenterX+sin(endangle)*Radius,
1990                 PieCenterY-cos(endangle)*Radius,
1991                 color);
1992     for (angle=endangle;angle-startangle>=step;angle-=step) {
1993         gfx_add_point(node,
1994                 PieCenterX+sin(angle)*Radius,
1995                 PieCenterY-cos(angle)*Radius );
1996     }
1997 }
1998
1999 int
2000 graph_size_location(image_desc_t *im, int elements, int piechart )
2001 {
2002     /* The actual size of the image to draw is determined from
2003     ** several sources.  The size given on the command line is
2004     ** the graph area but we need more as we have to draw labels
2005     ** and other things outside the graph area
2006     */
2007
2008     /* +-+-------------------------------------------+
2009     ** |l|.................title.....................|
2010     ** |e+--+-------------------------------+--------+
2011     ** |b| b|                               |        |
2012     ** |a| a|                               |  pie   |
2013     ** |l| l|          main graph area      | chart  |
2014     ** |.| .|                               |  area  |
2015     ** |t| y|                               |        |
2016     ** |r+--+-------------------------------+--------+
2017     ** |e|  | x-axis labels                 |        |
2018     ** |v+--+-------------------------------+--------+
2019     ** | |..............legends......................|
2020     ** +-+-------------------------------------------+
2021     */
2022     int Xvertical=0,    Yvertical=0,
2023         Xtitle   =0,    Ytitle   =0,
2024         Xylabel  =0,    Yylabel  =0,
2025         Xmain    =0,    Ymain    =0,
2026         Xpie     =0,    Ypie     =0,
2027         Xxlabel  =0,    Yxlabel  =0,
2028 #if 0
2029         Xlegend  =0,    Ylegend  =0,
2030 #endif
2031         Xspacing =10,   Yspacing =10;
2032
2033     if (im->ylegend[0] != '\0') {
2034         Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2035         Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2036     }
2037
2038     if (im->title[0] != '\0') {
2039         /* The title is placed "inbetween" two text lines so it
2040         ** automatically has some vertical spacing.  The horizontal
2041         ** spacing is added here, on each side.
2042         */
2043         Xtitle = gfx_get_text_width(im->canvas, 0,
2044                 im->text_prop[TEXT_PROP_TITLE].font,
2045                 im->text_prop[TEXT_PROP_TITLE].size,
2046                 im->tabwidth,
2047                 im->title) + 2*Xspacing;
2048         Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2049     }
2050
2051     if (elements) {
2052         Xmain=im->xsize;
2053         Ymain=im->ysize;
2054         if (im->draw_x_grid) {
2055             Xxlabel=Xmain;
2056             Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2057         }
2058         if (im->draw_y_grid) {
2059             Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2060             Yylabel=Ymain;
2061         }
2062     }
2063
2064     if (piechart) {
2065         im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2066         Xpie=im->piesize;
2067         Ypie=im->piesize;
2068     }
2069
2070     /* Now calculate the total size.  Insert some spacing where
2071        desired.  im->xorigin and im->yorigin need to correspond
2072        with the lower left corner of the main graph area or, if
2073        this one is not set, the imaginary box surrounding the
2074        pie chart area. */
2075
2076     /* The legend width cannot yet be determined, as a result we
2077     ** have problems adjusting the image to it.  For now, we just
2078     ** forget about it at all; the legend will have to fit in the
2079     ** size already allocated.
2080     */
2081     im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2082     if (Xmain) im->ximg += Xspacing;
2083     if (Xpie) im->ximg += Xspacing;
2084     im->xorigin = Xspacing + Xylabel;
2085     if (Xtitle > im->ximg) im->ximg = Xtitle;
2086     if (Xvertical) {
2087         im->ximg += Xvertical;
2088         im->xorigin += Xvertical;
2089     }
2090     xtr(im,0);
2091
2092     /* The vertical size is interesting... we need to compare
2093     ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2094     ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2095     ** start even thinking about Ylegend.
2096     **
2097     ** Do it in three portions: First calculate the inner part,
2098     ** then do the legend, then adjust the total height of the img.
2099     */
2100
2101     /* reserve space for main and/or pie */
2102     im->yimg = Ymain + Yxlabel;
2103     if (im->yimg < Ypie) im->yimg = Ypie;
2104     im->yorigin = im->yimg - Yxlabel;
2105     /* reserve space for the title *or* some padding above the graph */
2106     if (Ytitle) {
2107         im->yimg += Ytitle;
2108         im->yorigin += Ytitle;
2109     } else {
2110         im->yimg += Yspacing;
2111         im->yorigin += Yspacing;
2112     }
2113     /* reserve space for padding below the graph */
2114     im->yimg += Yspacing;
2115     ytr(im,DNAN);
2116
2117     /* Determine where to place the legends onto the image.
2118     ** Adjust im->yimg to match the space requirements.
2119     */
2120     if(leg_place(im)==-1)
2121         return -1;
2122
2123     /* last of three steps: check total height of image */
2124     if (im->yimg < Yvertical) im->yimg = Yvertical;
2125
2126 #if 0
2127     if (Xlegend > im->ximg) {
2128         im->ximg = Xlegend;
2129         /* reposition Pie */
2130 #endif
2131
2132     /* The pie is placed in the upper right hand corner,
2133     ** just below the title (if any) and with sufficient
2134     ** padding.
2135     */
2136     if (elements) {
2137         im->pie_x = im->ximg - Xspacing - Xpie/2;
2138         im->pie_y = im->yorigin-Ymain+Ypie/2;
2139     } else {
2140         im->pie_x = im->ximg/2;
2141         im->pie_y = im->yorigin-Ypie/2;
2142     }
2143
2144     return 0;
2145 }
2146
2147 /* draw that picture thing ... */
2148 int
2149 graph_paint(image_desc_t *im, char ***calcpr)
2150 {
2151   int i,ii;
2152   int lazy =     lazy_check(im);
2153   int piechart = 0;
2154   double PieStart=0.0;
2155   FILE  *fo;
2156   gfx_node_t *node;
2157   
2158   double areazero = 0.0;
2159   enum gf_en stack_gf = GF_PRINT;
2160   graph_desc_t *lastgdes = NULL;    
2161   
2162   /* if we are lazy and there is nothing to PRINT ... quit now */
2163   if (lazy && im->prt_c==0) return 0;
2164   
2165   /* pull the data from the rrd files ... */
2166   
2167   if(data_fetch(im)==-1)
2168     return -1;
2169   
2170   /* evaluate VDEF and CDEF operations ... */
2171   if(data_calc(im)==-1)
2172     return -1;
2173   
2174   /* check if we need to draw a piechart */
2175   for(i=0;i<im->gdes_c;i++){
2176     if (im->gdes[i].gf == GF_PART) {
2177       piechart=1;
2178       break;
2179     }
2180   }
2181
2182   /* calculate and PRINT and GPRINT definitions. We have to do it at
2183    * this point because it will affect the length of the legends
2184    * if there are no graph elements we stop here ... 
2185    * if we are lazy, try to quit ... 
2186    */
2187   i=print_calc(im,calcpr);
2188   if(i<0) return -1;
2189   if(((i==0)&&(piechart==0)) || lazy) return 0;
2190
2191   /* If there's only the pie chart to draw, signal this */
2192   if (i==0) piechart=2;
2193   
2194   /* get actual drawing data and find min and max values*/
2195   if(data_proc(im)==-1)
2196     return -1;
2197   
2198   if(!im->logarithmic){si_unit(im);}        /* identify si magnitude Kilo, Mega Giga ? */
2199   
2200   if(!im->rigid && ! im->logarithmic)
2201     expand_range(im);   /* make sure the upper and lower limit are
2202                            sensible values */
2203
2204   if (!calc_horizontal_grid(im))
2205     return -1;
2206   if (im->gridfit)
2207     apply_gridfit(im);
2208
2209 /**************************************************************
2210  *** Calculating sizes and locations became a bit confusing ***
2211  *** so I moved this into a separate function.              ***
2212  **************************************************************/
2213   if(graph_size_location(im,i,piechart)==-1)
2214     return -1;
2215
2216   /* the actual graph is created by going through the individual
2217      graph elements and then drawing them */
2218   
2219   node=gfx_new_area ( im->canvas,
2220                       0, 0,
2221                       im->ximg, 0,
2222                       im->ximg, im->yimg,
2223                       im->graph_col[GRC_BACK]);
2224
2225   gfx_add_point(node,0, im->yimg);
2226
2227   if (piechart != 2) {
2228     node=gfx_new_area ( im->canvas,
2229                       im->xorigin,             im->yorigin, 
2230                       im->xorigin + im->xsize, im->yorigin,
2231                       im->xorigin + im->xsize, im->yorigin-im->ysize,
2232                       im->graph_col[GRC_CANVAS]);
2233   
2234     gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2235
2236     if (im->minval > 0.0)
2237       areazero = im->minval;
2238     if (im->maxval < 0.0)
2239       areazero = im->maxval;
2240   
2241     axis_paint(im);
2242   }
2243
2244   if (piechart) {
2245     pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2246   }
2247
2248   for(i=0;i<im->gdes_c;i++){
2249     switch(im->gdes[i].gf){
2250     case GF_CDEF:
2251     case GF_VDEF:
2252     case GF_DEF:
2253     case GF_PRINT:
2254     case GF_GPRINT:
2255     case GF_COMMENT:
2256     case GF_HRULE:
2257     case GF_VRULE:
2258       break;
2259     case GF_TICK:
2260       for (ii = 0; ii < im->xsize; ii++)
2261         {
2262           if (!isnan(im->gdes[i].p_data[ii]) && 
2263               im->gdes[i].p_data[ii] > 0.0)
2264             { 
2265               /* generate a tick */
2266               gfx_new_line(im->canvas, im -> xorigin + ii, 
2267                            im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2268                            im -> xorigin + ii, 
2269                            im -> yorigin,
2270                            1.0,
2271                            im -> gdes[i].col );
2272             }
2273         }
2274       break;
2275     case GF_LINE:
2276     case GF_AREA:
2277       stack_gf = im->gdes[i].gf;
2278     case GF_STACK:          
2279       /* fix data points at oo and -oo */
2280       for(ii=0;ii<im->xsize;ii++){
2281         if (isinf(im->gdes[i].p_data[ii])){
2282           if (im->gdes[i].p_data[ii] > 0) {
2283             im->gdes[i].p_data[ii] = im->maxval ;
2284           } else {
2285             im->gdes[i].p_data[ii] = im->minval ;
2286           }                 
2287           
2288         }
2289       } /* for */
2290       
2291       if (im->gdes[i].col != 0x0){               
2292         /* GF_LINE and friend */
2293         if(stack_gf == GF_LINE ){
2294           node = NULL;
2295           for(ii=1;ii<im->xsize;ii++){
2296             if ( ! isnan(im->gdes[i].p_data[ii-1])
2297                  && ! isnan(im->gdes[i].p_data[ii])){
2298               if (node == NULL){
2299                 node = gfx_new_line(im->canvas,
2300                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2301                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2302                                     im->gdes[i].linewidth,
2303                                     im->gdes[i].col);
2304               } else {
2305                 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2306               }
2307             } else {
2308               node = NULL;
2309             }
2310           }
2311         } else {
2312           int area_start=-1;
2313           node = NULL;
2314           for(ii=1;ii<im->xsize;ii++){
2315             /* open an area */
2316             if ( ! isnan(im->gdes[i].p_data[ii-1])
2317                  && ! isnan(im->gdes[i].p_data[ii])){
2318               if (node == NULL){
2319                 float ybase = 0.0;
2320                 if (im->gdes[i].gf == GF_STACK) {
2321                   ybase = ytr(im,lastgdes->p_data[ii-1]);
2322                 } else {
2323                   ybase =  ytr(im,areazero);
2324                 }
2325                 area_start = ii-1;
2326                 node = gfx_new_area(im->canvas,
2327                                     ii-1+im->xorigin,ybase,
2328                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2329                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2330                                     im->gdes[i].col
2331                                     );
2332               } else {
2333                 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2334               }
2335             }
2336
2337             if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2338               /* GF_AREA STACK type*/
2339               if (im->gdes[i].gf == GF_STACK ) {
2340                 int iii;
2341                 for (iii=ii-1;iii>area_start;iii--){
2342                   gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2343                 }
2344               } else {
2345                 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2346               };
2347               node=NULL;
2348             };
2349           }             
2350         } /* else GF_LINE */
2351       } /* if color != 0x0 */
2352       /* make sure we do not run into trouble when stacking on NaN */
2353       for(ii=0;ii<im->xsize;ii++){
2354         if (isnan(im->gdes[i].p_data[ii])) {
2355           double ybase = 0.0;
2356           if (lastgdes) {
2357             ybase = ytr(im,lastgdes->p_data[ii-1]);
2358           };
2359           if (isnan(ybase) || !lastgdes ){
2360             ybase =  ytr(im,areazero);
2361           }
2362           im->gdes[i].p_data[ii] = ybase;
2363         }
2364       } 
2365       lastgdes = &(im->gdes[i]);                         
2366       break;
2367     case GF_PART:
2368       if(isnan(im->gdes[i].yrule)) /* fetch variable */
2369         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2370      
2371       if (finite(im->gdes[i].yrule)) {  /* even the fetched var can be NaN */
2372         pie_part(im,im->gdes[i].col,
2373                 im->pie_x,im->pie_y,im->piesize*0.4,
2374                 M_PI*2.0*PieStart/100.0,
2375                 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2376         PieStart += im->gdes[i].yrule;
2377       }
2378       break;
2379     } /* switch */
2380   }
2381   if (piechart==2) {
2382     im->draw_x_grid=0;
2383     im->draw_y_grid=0;
2384   }
2385   /* grid_paint also does the text */
2386   grid_paint(im);
2387   
2388   /* the RULES are the last thing to paint ... */
2389   for(i=0;i<im->gdes_c;i++){    
2390     
2391     switch(im->gdes[i].gf){
2392     case GF_HRULE:
2393       if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2394         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2395       };
2396       if(im->gdes[i].yrule >= im->minval
2397          && im->gdes[i].yrule <= im->maxval)
2398         gfx_new_line(im->canvas,
2399                      im->xorigin,ytr(im,im->gdes[i].yrule),
2400                      im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2401                      1.0,im->gdes[i].col); 
2402       break;
2403     case GF_VRULE:
2404       if(im->gdes[i].xrule == 0) { /* fetch variable */
2405         im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2406       };
2407       if(im->gdes[i].xrule >= im->start
2408          && im->gdes[i].xrule <= im->end)
2409         gfx_new_line(im->canvas,
2410                      xtr(im,im->gdes[i].xrule),im->yorigin,
2411                      xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2412                      1.0,im->gdes[i].col); 
2413       break;
2414     default:
2415       break;
2416     }
2417   }
2418
2419   
2420   if (strcmp(im->graphfile,"-")==0) {
2421 #ifdef WIN32
2422     /* Change translation mode for stdout to BINARY */
2423     _setmode( _fileno( stdout ), O_BINARY );
2424 #endif
2425     fo = stdout;
2426   } else {
2427     if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2428       rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2429                     strerror(errno));
2430       return (-1);
2431     }
2432   }
2433   gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2434   if (strcmp(im->graphfile,"-") != 0)
2435     fclose(fo);
2436   return 0;
2437 }
2438
2439
2440 /*****************************************************
2441  * graph stuff 
2442  *****************************************************/
2443
2444 int
2445 gdes_alloc(image_desc_t *im){
2446
2447     long def_step = (im->end-im->start)/im->xsize;
2448     
2449     if (im->step > def_step) /* step can be increassed ... no decreassed */
2450       def_step = im->step;
2451
2452     im->gdes_c++;
2453     
2454     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2455                                            * sizeof(graph_desc_t)))==NULL){
2456         rrd_set_error("realloc graph_descs");
2457         return -1;
2458     }
2459
2460
2461     im->gdes[im->gdes_c-1].step=def_step; 
2462     im->gdes[im->gdes_c-1].start=im->start; 
2463     im->gdes[im->gdes_c-1].end=im->end; 
2464     im->gdes[im->gdes_c-1].vname[0]='\0'; 
2465     im->gdes[im->gdes_c-1].data=NULL;
2466     im->gdes[im->gdes_c-1].ds_namv=NULL;
2467     im->gdes[im->gdes_c-1].data_first=0;
2468     im->gdes[im->gdes_c-1].p_data=NULL;
2469     im->gdes[im->gdes_c-1].rpnp=NULL;
2470     im->gdes[im->gdes_c-1].col = 0x0;
2471     im->gdes[im->gdes_c-1].legend[0]='\0';
2472     im->gdes[im->gdes_c-1].rrd[0]='\0';
2473     im->gdes[im->gdes_c-1].ds=-1;    
2474     im->gdes[im->gdes_c-1].p_data=NULL;    
2475     return 0;
2476 }
2477
2478 /* copies input untill the first unescaped colon is found
2479    or until input ends. backslashes have to be escaped as well */
2480 int
2481 scan_for_col(char *input, int len, char *output)
2482 {
2483     int inp,outp=0;
2484     for (inp=0; 
2485          inp < len &&
2486            input[inp] != ':' &&
2487            input[inp] != '\0';
2488          inp++){
2489       if (input[inp] == '\\' &&
2490           input[inp+1] != '\0' && 
2491           (input[inp+1] == '\\' ||
2492            input[inp+1] == ':')){
2493         output[outp++] = input[++inp];
2494       }
2495       else {
2496         output[outp++] = input[inp];
2497       }
2498     }
2499     output[outp] = '\0';
2500     return inp;
2501 }
2502
2503 /* Some surgery done on this function, it became ridiculously big.
2504 ** Things moved:
2505 ** - initializing     now in rrd_graph_init()
2506 ** - options parsing  now in rrd_graph_options()
2507 ** - script parsing   now in rrd_graph_script()
2508 */
2509 int 
2510 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2511 {
2512     image_desc_t   im;
2513
2514 #ifdef HAVE_TZSET
2515     tzset();
2516 #endif
2517 #ifdef HAVE_SETLOCALE
2518     setlocale(LC_ALL,"");
2519 #endif
2520             
2521             
2522     rrd_graph_init(&im);
2523
2524     rrd_graph_options(argc,argv,&im);
2525     if (rrd_test_error()) return -1;
2526     
2527     if (strlen(argv[optind])>=MAXPATH) {
2528         rrd_set_error("filename (including path) too long");
2529         return -1;
2530     }
2531     strncpy(im.graphfile,argv[optind],MAXPATH-1);
2532     im.graphfile[MAXPATH-1]='\0';
2533
2534     rrd_graph_script(argc,argv,&im);
2535     if (rrd_test_error()) return -1;
2536
2537     /* Everything is now read and the actual work can start */
2538
2539     (*prdata)=NULL;
2540     if (graph_paint(&im,prdata)==-1){
2541         im_free(&im);
2542         return -1;
2543     }
2544
2545     /* The image is generated and needs to be output.
2546     ** Also, if needed, print a line with information about the image.
2547     */
2548
2549     *xsize=im.ximg;
2550     *ysize=im.yimg;
2551     if (im.imginfo) {
2552         char *filename;
2553         if (!(*prdata)) {
2554             /* maybe prdata is not allocated yet ... lets do it now */
2555             if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2556                 rrd_set_error("malloc imginfo");
2557                 return -1; 
2558             };
2559         }
2560         if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2561          ==NULL){
2562             rrd_set_error("malloc imginfo");
2563             return -1;
2564         }
2565         filename=im.graphfile+strlen(im.graphfile);
2566         while(filename > im.graphfile) {
2567             if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2568             filename--;
2569         }
2570
2571         sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2572     }
2573     im_free(&im);
2574     return 0;
2575 }
2576
2577 void
2578 rrd_graph_init(image_desc_t *im)
2579 {
2580     int i;
2581
2582     im->xlab_user.minsec = -1;
2583     im->ximg=0;
2584     im->yimg=0;
2585     im->xsize = 400;
2586     im->ysize = 100;
2587     im->step = 0;
2588     im->ylegend[0] = '\0';
2589     im->title[0] = '\0';
2590     im->minval = DNAN;
2591     im->maxval = DNAN;    
2592     im->unitsexponent= 9999;
2593     im->extra_flags= 0;
2594     im->rigid = 0;
2595     im->gridfit = 1;
2596     im->imginfo = NULL;
2597     im->lazy = 0;
2598     im->logarithmic = 0;
2599     im->ygridstep = DNAN;
2600     im->draw_x_grid = 1;
2601     im->draw_y_grid = 1;
2602     im->base = 1000;
2603     im->prt_c = 0;
2604     im->gdes_c = 0;
2605     im->gdes = NULL;
2606     im->canvas = gfx_new_canvas();
2607
2608     for(i=0;i<DIM(graph_col);i++)
2609         im->graph_col[i]=graph_col[i];
2610
2611     for(i=0;i<DIM(text_prop);i++){        
2612       im->text_prop[i].size = text_prop[i].size;
2613       im->text_prop[i].font = text_prop[i].font;
2614     }
2615 }
2616
2617 void
2618 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2619 {
2620     int                 stroff;    
2621     char                *parsetime_error = NULL;
2622     char                scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2623     time_t              start_tmp=0,end_tmp=0;
2624     long                long_tmp;
2625     struct time_value   start_tv, end_tv;
2626     gfx_color_t         color;
2627
2628     parsetime("end-24h", &start_tv);
2629     parsetime("now", &end_tv);
2630
2631     while (1){
2632         static struct option long_options[] =
2633         {
2634             {"start",      required_argument, 0,  's'},
2635             {"end",        required_argument, 0,  'e'},
2636             {"x-grid",     required_argument, 0,  'x'},
2637             {"y-grid",     required_argument, 0,  'y'},
2638             {"vertical-label",required_argument,0,'v'},
2639             {"width",      required_argument, 0,  'w'},
2640             {"height",     required_argument, 0,  'h'},
2641             {"interlaced", no_argument,       0,  'i'},
2642             {"upper-limit",required_argument, 0,  'u'},
2643             {"lower-limit",required_argument, 0,  'l'},
2644             {"rigid",      no_argument,       0,  'r'},
2645             {"base",       required_argument, 0,  'b'},
2646             {"logarithmic",no_argument,       0,  'o'},
2647             {"color",      required_argument, 0,  'c'},
2648             {"font",       required_argument, 0,  'n'},
2649             {"title",      required_argument, 0,  't'},
2650             {"imginfo",    required_argument, 0,  'f'},
2651             {"imgformat",  required_argument, 0,  'a'},
2652             {"lazy",       no_argument,       0,  'z'},
2653             {"zoom",       required_argument, 0,  'm'},
2654             {"no-legend",  no_argument,       0,  'g'},
2655             {"alt-y-grid", no_argument,       0,   257 },
2656             {"alt-autoscale", no_argument,    0,   258 },
2657             {"alt-autoscale-max", no_argument,    0,   259 },
2658             {"units-exponent",required_argument, 0,  260},
2659             {"step",       required_argument, 0,   261},
2660             {"no-gridfit", no_argument,       0,   262},
2661             {0,0,0,0}};
2662         int option_index = 0;
2663         int opt;
2664
2665
2666         opt = getopt_long(argc, argv, 
2667                           "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2668                           long_options, &option_index);
2669
2670         if (opt == EOF)
2671             break;
2672         
2673         switch(opt) {
2674         case 257:
2675             im->extra_flags |= ALTYGRID;
2676             break;
2677         case 258:
2678             im->extra_flags |= ALTAUTOSCALE;
2679             break;
2680         case 259:
2681             im->extra_flags |= ALTAUTOSCALE_MAX;
2682             break;
2683         case 'g':
2684             im->extra_flags |= NOLEGEND;
2685             break;
2686         case 260:
2687             im->unitsexponent = atoi(optarg);
2688             break;
2689         case 261:
2690             im->step =  atoi(optarg);
2691             break;
2692         case 262:
2693             im->gridfit = 0;
2694             break;
2695         case 's':
2696             if ((parsetime_error = parsetime(optarg, &start_tv))) {
2697                 rrd_set_error( "start time: %s", parsetime_error );
2698                 return;
2699             }
2700             break;
2701         case 'e':
2702             if ((parsetime_error = parsetime(optarg, &end_tv))) {
2703                 rrd_set_error( "end time: %s", parsetime_error );
2704                 return;
2705             }
2706             break;
2707         case 'x':
2708             if(strcmp(optarg,"none") == 0){
2709               im->draw_x_grid=0;
2710               break;
2711             };
2712                 
2713             if(sscanf(optarg,
2714                       "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2715                       scan_gtm,
2716                       &im->xlab_user.gridst,
2717                       scan_mtm,
2718                       &im->xlab_user.mgridst,
2719                       scan_ltm,
2720                       &im->xlab_user.labst,
2721                       &im->xlab_user.precis,
2722                       &stroff) == 7 && stroff != 0){
2723                 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2724                 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2725                     rrd_set_error("unknown keyword %s",scan_gtm);
2726                     return;
2727                 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2728                     rrd_set_error("unknown keyword %s",scan_mtm);
2729                     return;
2730                 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2731                     rrd_set_error("unknown keyword %s",scan_ltm);
2732                     return;
2733                 } 
2734                 im->xlab_user.minsec = 1;
2735                 im->xlab_user.stst = im->xlab_form;
2736             } else {
2737                 rrd_set_error("invalid x-grid format");
2738                 return;
2739             }
2740             break;
2741         case 'y':
2742
2743             if(strcmp(optarg,"none") == 0){
2744               im->draw_y_grid=0;
2745               break;
2746             };
2747
2748             if(sscanf(optarg,
2749                       "%lf:%d",
2750                       &im->ygridstep,
2751                       &im->ylabfact) == 2) {
2752                 if(im->ygridstep<=0){
2753                     rrd_set_error("grid step must be > 0");
2754                     return;
2755                 } else if (im->ylabfact < 1){
2756                     rrd_set_error("label factor must be > 0");
2757                     return;
2758                 } 
2759             } else {
2760                 rrd_set_error("invalid y-grid format");
2761                 return;
2762             }
2763             break;
2764         case 'v':
2765             strncpy(im->ylegend,optarg,150);
2766             im->ylegend[150]='\0';
2767             break;
2768         case 'u':
2769             im->maxval = atof(optarg);
2770             break;
2771         case 'l':
2772             im->minval = atof(optarg);
2773             break;
2774         case 'b':
2775             im->base = atol(optarg);
2776             if(im->base != 1024 && im->base != 1000 ){
2777                 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2778                 return;
2779             }
2780             break;
2781         case 'w':
2782             long_tmp = atol(optarg);
2783             if (long_tmp < 10) {
2784                 rrd_set_error("width below 10 pixels");
2785                 return;
2786             }
2787             im->xsize = long_tmp;
2788             break;
2789         case 'h':
2790             long_tmp = atol(optarg);
2791             if (long_tmp < 10) {
2792                 rrd_set_error("height below 10 pixels");
2793                 return;
2794             }
2795             im->ysize = long_tmp;
2796             break;
2797         case 'i':
2798             im->canvas->interlaced = 1;
2799             break;
2800         case 'r':
2801             im->rigid = 1;
2802             break;
2803         case 'f':
2804             im->imginfo = optarg;
2805             break;
2806         case 'a':
2807             if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2808                 rrd_set_error("unsupported graphics format '%s'",optarg);
2809                 return;
2810             }
2811             break;
2812         case 'z':
2813             im->lazy = 1;
2814             break;
2815         case 'o':
2816             im->logarithmic = 1;
2817             if (isnan(im->minval))
2818                 im->minval=1;
2819             break;
2820         case 'c':
2821             if(sscanf(optarg,
2822                       "%10[A-Z]#%8lx",
2823                       col_nam,&color) == 2){
2824                 int ci;
2825                 if((ci=grc_conv(col_nam)) != -1){
2826                     im->graph_col[ci]=color;
2827                 }  else {
2828                   rrd_set_error("invalid color name '%s'",col_nam);
2829                 }
2830             } else {
2831                 rrd_set_error("invalid color def format");
2832                 return;
2833             }
2834             break;        
2835         case 'n':{
2836                         /* originally this used char *prop = "" and
2837                         ** char *font = "dummy" however this results
2838                         ** in a SEG fault, at least on RH7.1
2839                         **
2840                         ** The current implementation isn't proper
2841                         ** either, font is never freed and prop uses
2842                         ** a fixed width string
2843                         */
2844             char prop[100];
2845             double size = 1;
2846             char *font;
2847
2848             font=malloc(255);
2849             if(sscanf(optarg,
2850                                 "%10[A-Z]:%lf:%s",
2851                                 prop,&size,font) == 3){
2852                 int sindex;
2853                 if((sindex=text_prop_conv(prop)) != -1){
2854                     im->text_prop[sindex].size=size;              
2855                     im->text_prop[sindex].font=font;
2856                     if (sindex==0) { /* the default */
2857                         im->text_prop[TEXT_PROP_TITLE].size=size;
2858                         im->text_prop[TEXT_PROP_TITLE].font=font;
2859                         im->text_prop[TEXT_PROP_AXIS].size=size;
2860                         im->text_prop[TEXT_PROP_AXIS].font=font;
2861                         im->text_prop[TEXT_PROP_UNIT].size=size;
2862                         im->text_prop[TEXT_PROP_UNIT].font=font;
2863                         im->text_prop[TEXT_PROP_LEGEND].size=size;
2864                         im->text_prop[TEXT_PROP_LEGEND].font=font;
2865                     }
2866                 } else {
2867                     rrd_set_error("invalid fonttag '%s'",prop);
2868                     return;
2869                 }
2870             } else {
2871                 rrd_set_error("invalid text property format");
2872                 return;
2873             }
2874             break;          
2875         }
2876         case 'm':
2877             im->canvas->zoom = atof(optarg);
2878             if (im->canvas->zoom <= 0.0) {
2879                 rrd_set_error("zoom factor must be > 0");
2880                 return;
2881             }
2882           break;
2883         case 't':
2884             strncpy(im->title,optarg,150);
2885             im->title[150]='\0';
2886             break;
2887
2888         case '?':
2889             if (optopt != 0)
2890                 rrd_set_error("unknown option '%c'", optopt);
2891             else
2892                 rrd_set_error("unknown option '%s'",argv[optind-1]);
2893             return;
2894         }
2895     }
2896     
2897     if (optind >= argc) {
2898        rrd_set_error("missing filename");
2899        return;
2900     }
2901
2902     if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2903         rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");    
2904         return;
2905     }
2906
2907     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2908         /* error string is set in parsetime.c */
2909         return;
2910     }  
2911     
2912     if (start_tmp < 3600*24*365*10){
2913         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2914         return;
2915     }
2916     
2917     if (end_tmp < start_tmp) {
2918         rrd_set_error("start (%ld) should be less than end (%ld)", 
2919                start_tmp, end_tmp);
2920         return;
2921     }
2922     
2923     im->start = start_tmp;
2924     im->end = end_tmp;
2925 }
2926
2927 void
2928 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2929 {
2930     int         i;
2931     char        symname[100];
2932     int         linepass = 0; /* stack must follow LINE*, AREA or STACK */    
2933
2934     for (i=optind+1;i<argc;i++) {
2935         int             argstart=0;
2936         int             strstart=0;
2937         graph_desc_t    *gdp;
2938         char            *line;
2939         char            funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2940         double          d;
2941         double          linewidth;
2942         int             j,k,l,m;
2943
2944         /* Each command is one element from *argv[], we call this "line".
2945         **
2946         ** Each command defines the most current gdes inside struct im.
2947         ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2948         */
2949         gdes_alloc(im);
2950         gdp=&im->gdes[im->gdes_c-1];
2951         line=argv[i];
2952
2953         /* function:newvname=string[:ds-name:CF]        for xDEF
2954         ** function:vname[#color[:string]]              for LINEx,AREA,STACK
2955         ** function:vname#color[:num[:string]]          for TICK
2956         ** function:vname-or-num#color[:string]         for xRULE,PART
2957         ** function:vname:CF:string                     for xPRINT
2958         ** function:string                              for COMMENT
2959         */
2960         argstart=0;
2961
2962         sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2963         if (argstart==0) {
2964             rrd_set_error("Cannot parse function in line: %s",line);
2965             im_free(im);
2966             return;
2967         }
2968         if(sscanf(funcname,"LINE%lf",&linewidth)){
2969                 im->gdes[im->gdes_c-1].gf = GF_LINE;
2970                 im->gdes[im->gdes_c-1].linewidth = linewidth;
2971         } else {
2972           if ((gdp->gf=gf_conv(funcname))==-1) {
2973               rrd_set_error("'%s' is not a valid function name",funcname);
2974               im_free(im);
2975               return;
2976           }
2977         }
2978
2979         /* If the error string is set, we exit at the end of the switch */
2980         switch (gdp->gf) {
2981             case GF_COMMENT:
2982                 if (rrd_graph_legend(gdp,&line[argstart])==0)
2983                     rrd_set_error("Cannot parse comment in line: %s",line);
2984                 break;
2985             case GF_PART:
2986             case GF_VRULE:
2987             case GF_HRULE:
2988                 j=k=l=m=0;
2989                 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
2990                 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
2991                 if (k+m==0) {
2992                     rrd_set_error("Cannot parse name or num in line: %s",line);
2993                     break;
2994                 }
2995                 if (j!=0) {
2996                     gdp->xrule=d;
2997                     gdp->yrule=d;
2998                     argstart+=j;
2999                 } else if (!rrd_graph_check_vname(im,vname,line)) {
3000                     gdp->xrule=0;
3001                     gdp->yrule=DNAN;
3002                     argstart+=l;
3003                 } else break; /* exit due to wrong vname */
3004                 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
3005                 argstart+=j;
3006                 if (strlen(&line[argstart])!=0) {
3007                     if (rrd_graph_legend(gdp,&line[++argstart])==0)
3008                         rrd_set_error("Cannot parse comment in line: %s",line);
3009                 }
3010                 break;
3011             case GF_STACK:
3012                 if (linepass==0) {
3013                     rrd_set_error("STACK must follow another graphing element");
3014                     break;
3015                 }
3016             case GF_LINE:
3017             case GF_AREA:
3018             case GF_TICK:
3019                 j=k=0;
3020                 linepass=1;
3021                 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
3022                 if (j+1!=k)
3023                     rrd_set_error("Cannot parse vname in line: %s",line);
3024                 else if (rrd_graph_check_vname(im,vname,line))
3025                     rrd_set_error("Undefined vname '%s' in line: %s",line);
3026                 else
3027                     k=rrd_graph_color(im,&line[argstart],line,1);
3028                 if (rrd_test_error()) break;
3029                 argstart=argstart+j+k;
3030                 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
3031                     j=0;
3032                     sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
3033                     argstart+=j;
3034                 }
3035                 if (strlen(&line[argstart])!=0)
3036                     if (rrd_graph_legend(gdp,&line[++argstart])==0)
3037                         rrd_set_error("Cannot parse legend in line: %s",line);
3038                 break;
3039             case GF_PRINT:
3040                 im->prt_c++;
3041             case GF_GPRINT:
3042                 j=0;
3043                 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
3044                 if (j==0) {
3045                     rrd_set_error("Cannot parse vname in line: '%s'",line);
3046                     break;
3047                 }
3048                 argstart+=j;
3049                 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
3050                 j=0;
3051                 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
3052
3053                 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
3054 #define VIDX im->gdes[gdp->vidx]
3055                 switch (k) {
3056                     case -1: /* looks CF but is not really CF */
3057                         if (VIDX.gf == GF_VDEF) rrd_clear_error();
3058                         break;
3059                     case  0: /* CF present and correct */
3060                         if (VIDX.gf == GF_VDEF)
3061                             rrd_set_error("Don't use CF when printing VDEF");
3062                         argstart+=j;
3063                         break;
3064                     case  1: /* CF not present */
3065                         if (VIDX.gf == GF_VDEF) rrd_clear_error();
3066                         else rrd_set_error("Printing DEF or CDEF needs CF");
3067                         break;
3068                     default:
3069                         rrd_set_error("Oops, bug in GPRINT scanning");
3070                 }
3071 #undef VIDX
3072                 if (rrd_test_error()) break;
3073
3074                 if (strlen(&line[argstart])!=0) {
3075                     if (rrd_graph_legend(gdp,&line[argstart])==0)
3076                         rrd_set_error("Cannot parse legend in line: %s",line);
3077                 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
3078                 strcpy(gdp->format, gdp->legend);
3079                 break;
3080             case GF_DEF:
3081             case GF_VDEF:
3082             case GF_CDEF:
3083                 j=0;
3084                 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
3085                 if (j==0) {
3086                     rrd_set_error("Could not parse line: %s",line);
3087                     break;
3088                 }
3089                 if (find_var(im,gdp->vname)!=-1) {
3090                     rrd_set_error("Variable '%s' in line '%s' already in use\n",
3091                                                         gdp->vname,line);
3092                     break;
3093                 }
3094                 argstart+=j;
3095                 switch (gdp->gf) {
3096                     case GF_DEF:
3097                         argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
3098                         j=k=0;
3099                         sscanf(&line[argstart],
3100                                 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
3101                                 gdp->ds_nam, symname, &j, &k);
3102                         if ((j==0)||(k!=0)) {
3103                             rrd_set_error("Cannot parse DS or CF in '%s'",line);
3104                             break;
3105                         }
3106                         rrd_graph_check_CF(im,symname,line);
3107                         break;
3108                     case GF_VDEF:
3109                         j=0;
3110                         sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
3111                         if (j==0) {
3112                             rrd_set_error("Cannot parse vname in line '%s'",line);
3113                             break;
3114                         }
3115                         argstart+=j;
3116                         if (rrd_graph_check_vname(im,vname,line)) return;
3117                         if (       im->gdes[gdp->vidx].gf != GF_DEF
3118                                 && im->gdes[gdp->vidx].gf != GF_CDEF) {
3119                             rrd_set_error("variable '%s' not DEF nor "
3120                                 "CDEF in VDEF '%s'", vname,gdp->vname);
3121                             break;
3122                         }
3123                         vdef_parse(gdp,&line[argstart+strstart]);
3124                         break;
3125                     case GF_CDEF:
3126                         if (strstr(&line[argstart],":")!=NULL) {
3127                             rrd_set_error("Error in RPN, line: %s",line);
3128                             break;
3129                         }
3130                         if ((gdp->rpnp = rpn_parse(
3131                                                 (void *)im,
3132                                                 &line[argstart],
3133                                                 &find_var_wrapper)
3134                                 )==NULL)
3135                             rrd_set_error("invalid rpn expression in: %s",line);
3136                         break;
3137                     default: break;
3138                 }
3139                 break;
3140             default: rrd_set_error("Big oops");
3141         }
3142         if (rrd_test_error()) {
3143             im_free(im);
3144             return;
3145         }
3146     }
3147
3148     if (im->gdes_c==0){
3149         rrd_set_error("can't make a graph without contents");
3150         im_free(im); /* ??? is this set ??? */
3151         return; 
3152     }
3153 }
3154 int
3155 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3156 {
3157     if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3158         rrd_set_error("Unknown variable '%s' in %s",varname,err);
3159         return -1;
3160     }
3161     return 0;
3162 }
3163 int
3164 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3165 {
3166     char *color;
3167     graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3168
3169     color=strstr(var,"#");
3170     if (color==NULL) {
3171         if (optional==0) {
3172             rrd_set_error("Found no color in %s",err);
3173             return 0;
3174         }
3175         return 0;
3176     } else {
3177         int n=0;
3178         char *rest;
3179         gfx_color_t    col;
3180
3181         rest=strstr(color,":");
3182         if (rest!=NULL)
3183             n=rest-color;
3184         else
3185             n=strlen(color);
3186
3187         switch (n) {
3188             case 7:
3189                 sscanf(color,"#%6lx%n",&col,&n);
3190                 col = (col << 8) + 0xff /* shift left by 8 */;
3191                 if (n!=7) rrd_set_error("Color problem in %s",err);
3192                 break;
3193             case 9:
3194                 sscanf(color,"#%8lx%n",&col,&n);
3195                 if (n==9) break;
3196             default:
3197                 rrd_set_error("Color problem in %s",err);
3198         }
3199         if (rrd_test_error()) return 0;
3200         gdp->col = col;
3201         return n;
3202     }
3203 }
3204 int
3205 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
3206 {
3207     if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
3208         rrd_set_error("Unknown CF '%s' in %s",symname,err);
3209         return -1;
3210     }
3211     return 0;
3212 }
3213 int
3214 rrd_graph_legend(graph_desc_t *gdp, char *line)
3215 {
3216     int i;
3217
3218     i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3219
3220     return (strlen(&line[i])==0);
3221 }
3222
3223
3224 int bad_format(char *fmt) {
3225         char *ptr;
3226         int n=0;
3227
3228         ptr = fmt;
3229         while (*ptr != '\0') {
3230                 if (*ptr == '%') {ptr++;
3231                         if (*ptr == '\0') return 1;
3232                         while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') { 
3233                                 ptr++;
3234                         }
3235                         if (*ptr == '\0') return 1;
3236                         if (*ptr == 'l') {
3237                                 ptr++;
3238                                 n++;
3239                                 if (*ptr == '\0') return 1;
3240                                 if (*ptr == 'e' || *ptr == 'f') { 
3241                                         ptr++; 
3242                                         } else { return 1; }
3243                         }
3244                         else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3245                         else { return 1; }
3246                 } else {
3247                         ++ptr;
3248                 }
3249         }
3250         return (n!=1);
3251 }
3252 int
3253 vdef_parse(gdes,str)
3254 struct graph_desc_t *gdes;
3255 char *str;
3256 {
3257     /* A VDEF currently is either "func" or "param,func"
3258      * so the parsing is rather simple.  Change if needed.
3259      */
3260     double      param;
3261     char        func[30];
3262     int         n;
3263     
3264     n=0;
3265     sscanf(str,"%le,%29[A-Z]%n",&param,func,&n);
3266     if (n==strlen(str)) { /* matched */
3267         ;
3268     } else {
3269         n=0;
3270         sscanf(str,"%29[A-Z]%n",func,&n);
3271         if (n==strlen(str)) { /* matched */
3272             param=DNAN;
3273         } else {
3274             rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3275                 ,str
3276                 ,gdes->vname
3277                 );
3278             return -1;
3279         }
3280     }
3281     if          (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3282     else if     (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3283     else if     (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3284     else if     (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3285     else if     (!strcmp("TOTAL",  func)) gdes->vf.op = VDEF_TOTAL;
3286     else if     (!strcmp("FIRST",  func)) gdes->vf.op = VDEF_FIRST;
3287     else if     (!strcmp("LAST",   func)) gdes->vf.op = VDEF_LAST;
3288     else {
3289         rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3290             ,func
3291             ,gdes->vname
3292             );
3293         return -1;
3294     };
3295
3296     switch (gdes->vf.op) {
3297         case VDEF_PERCENT:
3298             if (isnan(param)) { /* no parameter given */
3299                 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3300                     ,func
3301                     ,gdes->vname
3302                     );
3303                 return -1;
3304             };
3305             if (param>=0.0 && param<=100.0) {
3306                 gdes->vf.param = param;
3307                 gdes->vf.val   = DNAN;  /* undefined */
3308                 gdes->vf.when  = 0;     /* undefined */
3309             } else {
3310                 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3311                     ,param
3312                     ,gdes->vname
3313                     );
3314                 return -1;
3315             };
3316             break;
3317         case VDEF_MAXIMUM:
3318         case VDEF_AVERAGE:
3319         case VDEF_MINIMUM:
3320         case VDEF_TOTAL:
3321         case VDEF_FIRST:
3322         case VDEF_LAST:
3323             if (isnan(param)) {
3324                 gdes->vf.param = DNAN;
3325                 gdes->vf.val   = DNAN;
3326                 gdes->vf.when  = 0;
3327             } else {
3328                 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3329                     ,func
3330                     ,gdes->vname
3331                     );
3332                 return -1;
3333             };
3334             break;
3335     };
3336     return 0;
3337 }
3338 int
3339 vdef_calc(im,gdi)
3340 image_desc_t *im;
3341 int gdi;
3342 {
3343     graph_desc_t        *src,*dst;
3344     rrd_value_t         *data;
3345     long                step,steps;
3346
3347     dst = &im->gdes[gdi];
3348     src = &im->gdes[dst->vidx];
3349     data = src->data + src->ds;
3350     steps = (src->end - src->start) / src->step;
3351
3352 #if 0
3353 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3354     ,src->start
3355     ,src->end
3356     ,steps
3357     );
3358 #endif
3359
3360     switch (dst->vf.op) {
3361         case VDEF_PERCENT: {
3362                 rrd_value_t *   array;
3363                 int             field;
3364
3365
3366                 if ((array = malloc(steps*sizeof(double)))==NULL) {
3367                     rrd_set_error("malloc VDEV_PERCENT");
3368                     return -1;
3369                 }
3370                 for (step=0;step < steps; step++) {
3371                     array[step]=data[step*src->ds_cnt];
3372                 }
3373                 qsort(array,step,sizeof(double),vdef_percent_compar);
3374
3375                 field = (steps-1)*dst->vf.param/100;
3376                 dst->vf.val  = array[field];
3377                 dst->vf.when = 0;       /* no time component */
3378 #if 0
3379 for(step=0;step<steps;step++)
3380 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3381 #endif
3382             }
3383             break;
3384         case VDEF_MAXIMUM:
3385             step=0;
3386             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3387             if (step == steps) {
3388                 dst->vf.val  = DNAN;
3389                 dst->vf.when = 0;
3390             } else {
3391                 dst->vf.val  = data[step*src->ds_cnt];
3392                 dst->vf.when = src->start + (step+1)*src->step;
3393             }
3394             while (step != steps) {
3395                 if (finite(data[step*src->ds_cnt])) {
3396                     if (data[step*src->ds_cnt] > dst->vf.val) {
3397                         dst->vf.val  = data[step*src->ds_cnt];
3398                         dst->vf.when = src->start + (step+1)*src->step;
3399                     }
3400                 }
3401                 step++;
3402             }
3403             break;
3404         case VDEF_TOTAL:
3405         case VDEF_AVERAGE: {
3406             int cnt=0;
3407             double sum=0.0;
3408             for (step=0;step<steps;step++) {
3409                 if (finite(data[step*src->ds_cnt])) {
3410                     sum += data[step*src->ds_cnt];
3411                     cnt ++;
3412                 };
3413             }
3414             if (cnt) {
3415                 if (dst->vf.op == VDEF_TOTAL) {
3416                     dst->vf.val  = sum*src->step;
3417                     dst->vf.when = cnt*src->step;       /* not really "when" */
3418                 } else {
3419                     dst->vf.val = sum/cnt;
3420                     dst->vf.when = 0;   /* no time component */
3421                 };
3422             } else {
3423                 dst->vf.val  = DNAN;
3424                 dst->vf.when = 0;
3425             }
3426             }
3427             break;
3428         case VDEF_MINIMUM:
3429             step=0;
3430             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3431             if (step == steps) {
3432                 dst->vf.val  = DNAN;
3433                 dst->vf.when = 0;
3434             } else {
3435                 dst->vf.val  = data[step*src->ds_cnt];
3436                 dst->vf.when = src->start + (step+1)*src->step;
3437             }
3438             while (step != steps) {
3439                 if (finite(data[step*src->ds_cnt])) {
3440                     if (data[step*src->ds_cnt] < dst->vf.val) {
3441                         dst->vf.val  = data[step*src->ds_cnt];
3442                         dst->vf.when = src->start + (step+1)*src->step;
3443                     }
3444                 }
3445                 step++;
3446             }
3447             break;
3448         case VDEF_FIRST:
3449             /* The time value returned here is one step before the
3450              * actual time value.  This is the start of the first
3451              * non-NaN interval.
3452              */
3453             step=0;
3454             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3455             if (step == steps) { /* all entries were NaN */
3456                 dst->vf.val  = DNAN;
3457                 dst->vf.when = 0;
3458             } else {
3459                 dst->vf.val  = data[step*src->ds_cnt];
3460                 dst->vf.when = src->start + step*src->step;
3461             }
3462             break;
3463         case VDEF_LAST:
3464             /* The time value returned here is the
3465              * actual time value.  This is the end of the last
3466              * non-NaN interval.
3467              */
3468             step=steps-1;
3469             while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3470             if (step < 0) { /* all entries were NaN */
3471                 dst->vf.val  = DNAN;
3472                 dst->vf.when = 0;
3473             } else {
3474                 dst->vf.val  = data[step*src->ds_cnt];
3475                 dst->vf.when = src->start + (step+1)*src->step;
3476             }
3477             break;
3478     }
3479     return 0;
3480 }
3481
3482 /* NaN < -INF < finite_values < INF */
3483 int
3484 vdef_percent_compar(a,b)
3485 const void *a,*b;
3486 {
3487     /* Equality is not returned; this doesn't hurt except
3488      * (maybe) for a little performance.
3489      */
3490
3491     /* First catch NaN values. They are smallest */
3492     if (isnan( *(double *)a )) return -1;
3493     if (isnan( *(double *)b )) return  1;
3494
3495     /* NaN doesn't reach this part so INF and -INF are extremes.
3496      * The sign from isinf() is compatible with the sign we return
3497      */
3498     if (isinf( *(double *)a )) return isinf( *(double *)a );
3499     if (isinf( *(double *)b )) return isinf( *(double *)b );
3500
3501     /* If we reach this, both values must be finite */
3502     if ( *(double *)a < *(double *)b ) return -1; else return 1;
3503 }