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