--full-size-mode with and without --no-legend should work now -- Matthew Chambers
[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         if (im->extra_flags & NOLEGEND)
2714         {
2715             /* set dimensions correctly if using full size mode with no legend */
2716             im->yorigin = im->yimg - im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2717             Ymain = im->yorigin;
2718         } else
2719         {
2720             /* Determine where to place the legends onto the image.
2721             ** Set Ymain and adjust im->yorigin to match the space requirements.
2722             */
2723             if (leg_place(im, &Ymain) == -1)
2724                return -1;
2725         }
2726
2727 #ifdef WITH_PIECHART
2728         /* if (im->yimg < Ypie) im->yimg = Ypie; * not sure what do about this */
2729 #endif
2730
2731         /* remove title space *or* some padding above the graph from the main graph area */
2732         if (Ytitle) {
2733             Ymain -= Ytitle;
2734         } else {
2735             Ymain -= 1.5 * Yspacing;
2736         }
2737
2738         /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2739         if (im->watermark[0] != '\0') {
2740             Ymain -= Ywatermark;
2741         }
2742
2743         im->ysize = Ymain;
2744
2745     } else {            /* dimension options -width and -height refer to the dimensions of the main graph area */
2746
2747         /* The actual size of the image to draw is determined from
2748          ** several sources.  The size given on the command line is
2749          ** the graph area but we need more as we have to draw labels
2750          ** and other things outside the graph area.
2751          */
2752
2753         if (im->ylegend[0] != '\0') {
2754             Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2755         }
2756
2757
2758         if (im->title[0] != '\0') {
2759             /* The title is placed "inbetween" two text lines so it
2760              ** automatically has some vertical spacing.  The horizontal
2761              ** spacing is added here, on each side.
2762              */
2763             /* don't care for the with of the title
2764                Xtitle = gfx_get_text_width(im->canvas, 0,
2765                im->text_prop[TEXT_PROP_TITLE].font,
2766                im->text_prop[TEXT_PROP_TITLE].size,
2767                im->tabwidth,
2768                im->title, 0) + 2*Xspacing; */
2769             Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2770         }
2771
2772         if (elements) {
2773             Xmain = im->xsize;
2774             Ymain = im->ysize;
2775         }
2776 #ifdef WITH_PIECHART
2777         if (piechart) {
2778             im->piesize = im->xsize < im->ysize ? im->xsize : im->ysize;
2779             Xpie = im->piesize;
2780             Ypie = im->piesize;
2781         }
2782 #endif
2783
2784         /* Now calculate the total size.  Insert some spacing where
2785            desired.  im->xorigin and im->yorigin need to correspond
2786            with the lower left corner of the main graph area or, if
2787            this one is not set, the imaginary box surrounding the
2788            pie chart area. */
2789
2790         /* The legend width cannot yet be determined, as a result we
2791          ** have problems adjusting the image to it.  For now, we just
2792          ** forget about it at all; the legend will have to fit in the
2793          ** size already allocated.
2794          */
2795         im->ximg = Xylabel + Xmain + 2 * Xspacing;
2796
2797 #ifdef WITH_PIECHART
2798         im->ximg += Xpie;
2799 #endif
2800
2801         if (Xmain)
2802             im->ximg += Xspacing;
2803 #ifdef WITH_PIECHART
2804         if (Xpie)
2805             im->ximg += Xspacing;
2806 #endif
2807
2808         im->xorigin = Xspacing + Xylabel;
2809
2810         /* the length of the title should not influence with width of the graph
2811            if (Xtitle > im->ximg) im->ximg = Xtitle; */
2812
2813         if (Xvertical) {    /* unit description */
2814             im->ximg += Xvertical;
2815             im->xorigin += Xvertical;
2816         }
2817         xtr(im, 0);
2818
2819         /* The vertical size is interesting... we need to compare
2820          ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with 
2821          ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2822          ** in order to start even thinking about Ylegend or Ywatermark.
2823          **
2824          ** Do it in three portions: First calculate the inner part,
2825          ** then do the legend, then adjust the total height of the img,
2826          ** adding space for a watermark if one exists;
2827          */
2828
2829         /* reserve space for main and/or pie */
2830
2831         im->yimg = Ymain + Yxlabel;
2832
2833 #ifdef WITH_PIECHART
2834         if (im->yimg < Ypie)
2835             im->yimg = Ypie;
2836 #endif
2837
2838         im->yorigin = im->yimg - Yxlabel;
2839
2840         /* reserve space for the title *or* some padding above the graph */
2841         if (Ytitle) {
2842             im->yimg += Ytitle;
2843             im->yorigin += Ytitle;
2844         } else {
2845             im->yimg += 1.5 * Yspacing;
2846             im->yorigin += 1.5 * Yspacing;
2847         }
2848         /* reserve space for padding below the graph */
2849         im->yimg += Yspacing;
2850
2851         /* Determine where to place the legends onto the image.
2852          ** Adjust im->yimg to match the space requirements.
2853          */
2854         if (leg_place(im, 0) == -1)
2855             return -1;
2856
2857         if (im->watermark[0] != '\0') {
2858             im->yimg += Ywatermark;
2859         }
2860     }
2861
2862 #if 0
2863     if (Xlegend > im->ximg) {
2864         im->ximg = Xlegend;
2865         /* reposition Pie */
2866     }
2867 #endif
2868
2869 #ifdef WITH_PIECHART
2870     /* The pie is placed in the upper right hand corner,
2871      ** just below the title (if any) and with sufficient
2872      ** padding.
2873      */
2874     if (elements) {
2875         im->pie_x = im->ximg - Xspacing - Xpie / 2;
2876         im->pie_y = im->yorigin - Ymain + Ypie / 2;
2877     } else {
2878         im->pie_x = im->ximg / 2;
2879         im->pie_y = im->yorigin - Ypie / 2;
2880     }
2881 #endif
2882
2883     ytr(im, DNAN);
2884     return 0;
2885 }
2886
2887 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2888 /* yes we are loosing precision by doing tos with floats instead of doubles
2889    but it seems more stable this way. */
2890
2891
2892 /* draw that picture thing ... */
2893 int graph_paint(
2894     image_desc_t *im,
2895     char ***calcpr)
2896 {
2897     int       i, ii;
2898     int       lazy = lazy_check(im);
2899
2900 #ifdef WITH_PIECHART
2901     int       piechart = 0;
2902     double    PieStart = 0.0;
2903 #endif
2904     FILE     *fo;
2905     gfx_node_t *node;
2906
2907     double    areazero = 0.0;
2908     graph_desc_t *lastgdes = NULL;
2909
2910     /* if we are lazy and there is nothing to PRINT ... quit now */
2911     if (lazy && im->prt_c == 0)
2912         return 0;
2913
2914     /* pull the data from the rrd files ... */
2915
2916     if (data_fetch(im) == -1)
2917         return -1;
2918
2919     /* evaluate VDEF and CDEF operations ... */
2920     if (data_calc(im) == -1)
2921         return -1;
2922
2923 #ifdef WITH_PIECHART
2924     /* check if we need to draw a piechart */
2925     for (i = 0; i < im->gdes_c; i++) {
2926         if (im->gdes[i].gf == GF_PART) {
2927             piechart = 1;
2928             break;
2929         }
2930     }
2931 #endif
2932
2933     /* calculate and PRINT and GPRINT definitions. We have to do it at
2934      * this point because it will affect the length of the legends
2935      * if there are no graph elements we stop here ... 
2936      * if we are lazy, try to quit ... 
2937      */
2938     i = print_calc(im, calcpr);
2939     if (i < 0)
2940         return -1;
2941     if (((i == 0)
2942 #ifdef WITH_PIECHART
2943          && (piechart == 0)
2944 #endif
2945         ) || lazy)
2946         return 0;
2947
2948 #ifdef WITH_PIECHART
2949     /* If there's only the pie chart to draw, signal this */
2950     if (i == 0)
2951         piechart = 2;
2952 #endif
2953
2954 /**************************************************************
2955  *** Calculating sizes and locations became a bit confusing ***
2956  *** so I moved this into a separate function.              ***
2957  **************************************************************/
2958     if (graph_size_location(im, i
2959 #ifdef WITH_PIECHART
2960                             , piechart
2961 #endif
2962         ) == -1)
2963         return -1;
2964
2965     /* get actual drawing data and find min and max values */
2966     if (data_proc(im) == -1)
2967         return -1;
2968
2969     if (!im->logarithmic) {
2970         si_unit(im);
2971     }
2972     /* identify si magnitude Kilo, Mega Giga ? */
2973     if (!im->rigid && !im->logarithmic)
2974         expand_range(im);   /* make sure the upper and lower limit are
2975                                sensible values */
2976
2977     if (!calc_horizontal_grid(im))
2978         return -1;
2979
2980     if (im->gridfit)
2981         apply_gridfit(im);
2982
2983     /* the actual graph is created by going through the individual
2984        graph elements and then drawing them */
2985
2986     node = gfx_new_area(im->canvas,
2987                         0, 0,
2988                         0, im->yimg,
2989                         im->ximg, im->yimg, im->graph_col[GRC_BACK]);
2990
2991     gfx_add_point(node, im->ximg, 0);
2992
2993 #ifdef WITH_PIECHART
2994     if (piechart != 2) {
2995 #endif
2996         node = gfx_new_area(im->canvas,
2997                             im->xorigin, im->yorigin,
2998                             im->xorigin + im->xsize, im->yorigin,
2999                             im->xorigin + im->xsize, im->yorigin - im->ysize,
3000                             im->graph_col[GRC_CANVAS]);
3001
3002         gfx_add_point(node, im->xorigin, im->yorigin - im->ysize);
3003
3004         if (im->minval > 0.0)
3005             areazero = im->minval;
3006         if (im->maxval < 0.0)
3007             areazero = im->maxval;
3008 #ifdef WITH_PIECHART
3009     }
3010 #endif
3011
3012 #ifdef WITH_PIECHART
3013     if (piechart) {
3014         pie_part(im, im->graph_col[GRC_CANVAS], im->pie_x, im->pie_y,
3015                  im->piesize * 0.5, 0, 2 * M_PI);
3016     }
3017 #endif
3018
3019     for (i = 0; i < im->gdes_c; i++) {
3020         switch (im->gdes[i].gf) {
3021         case GF_CDEF:
3022         case GF_VDEF:
3023         case GF_DEF:
3024         case GF_PRINT:
3025         case GF_GPRINT:
3026         case GF_COMMENT:
3027         case GF_HRULE:
3028         case GF_VRULE:
3029         case GF_XPORT:
3030         case GF_SHIFT:
3031             break;
3032         case GF_TICK:
3033             for (ii = 0; ii < im->xsize; ii++) {
3034                 if (!isnan(im->gdes[i].p_data[ii]) &&
3035                     im->gdes[i].p_data[ii] != 0.0) {
3036                     if (im->gdes[i].yrule > 0) {
3037                         gfx_new_line(im->canvas,
3038                                      im->xorigin + ii, im->yorigin,
3039                                      im->xorigin + ii,
3040                                      im->yorigin -
3041                                      im->gdes[i].yrule * im->ysize, 1.0,
3042                                      im->gdes[i].col);
3043                     } else if (im->gdes[i].yrule < 0) {
3044                         gfx_new_line(im->canvas,
3045                                      im->xorigin + ii,
3046                                      im->yorigin - im->ysize,
3047                                      im->xorigin + ii,
3048                                      im->yorigin - (1 -
3049                                                     im->gdes[i].yrule) *
3050                                      im->ysize, 1.0, im->gdes[i].col);
3051
3052                     }
3053                 }
3054             }
3055             break;
3056         case GF_LINE:
3057         case GF_AREA:
3058             /* fix data points at oo and -oo */
3059             for (ii = 0; ii < im->xsize; ii++) {
3060                 if (isinf(im->gdes[i].p_data[ii])) {
3061                     if (im->gdes[i].p_data[ii] > 0) {
3062                         im->gdes[i].p_data[ii] = im->maxval;
3063                     } else {
3064                         im->gdes[i].p_data[ii] = im->minval;
3065                     }
3066
3067                 }
3068             }           /* for */
3069
3070             /* *******************************************************
3071                a           ___. (a,t) 
3072                |   |    ___
3073                ____|   |   |   |
3074                |       |___|
3075                -------|--t-1--t--------------------------------      
3076
3077                if we know the value at time t was a then 
3078                we draw a square from t-1 to t with the value a.
3079
3080                ********************************************************* */
3081             if (im->gdes[i].col != 0x0) {
3082                 /* GF_LINE and friend */
3083                 if (im->gdes[i].gf == GF_LINE) {
3084                     double    last_y = 0.0;
3085
3086                     node = NULL;
3087                     for (ii = 1; ii < im->xsize; ii++) {
3088                         if (isnan(im->gdes[i].p_data[ii])
3089                             || (im->slopemode == 1
3090                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3091                             node = NULL;
3092                             continue;
3093                         }
3094                         if (node == NULL) {
3095                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3096                             if (im->slopemode == 0) {
3097                                 node = gfx_new_line(im->canvas,
3098                                                     ii - 1 + im->xorigin,
3099                                                     last_y, ii + im->xorigin,
3100                                                     last_y,
3101                                                     im->gdes[i].linewidth,
3102                                                     im->gdes[i].col);
3103                             } else {
3104                                 node = gfx_new_line(im->canvas,
3105                                                     ii - 1 + im->xorigin,
3106                                                     ytr(im,
3107                                                         im->gdes[i].
3108                                                         p_data[ii - 1]),
3109                                                     ii + im->xorigin, last_y,
3110                                                     im->gdes[i].linewidth,
3111                                                     im->gdes[i].col);
3112                             }
3113                         } else {
3114                             double    new_y = ytr(im, im->gdes[i].p_data[ii]);
3115
3116                             if (im->slopemode == 0
3117                                 && !AlmostEqual2sComplement(new_y, last_y,
3118                                                             4)) {
3119                                 gfx_add_point(node, ii - 1 + im->xorigin,
3120                                               new_y);
3121                             };
3122                             last_y = new_y;
3123                             gfx_add_point(node, ii + im->xorigin, new_y);
3124                         };
3125
3126                     }
3127                 } else {
3128                     int       idxI = -1;
3129                     double   *foreY = malloc(sizeof(double) * im->xsize * 2);
3130                     double   *foreX = malloc(sizeof(double) * im->xsize * 2);
3131                     double   *backY = malloc(sizeof(double) * im->xsize * 2);
3132                     double   *backX = malloc(sizeof(double) * im->xsize * 2);
3133                     int       drawem = 0;
3134
3135                     for (ii = 0; ii <= im->xsize; ii++) {
3136                         double    ybase, ytop;
3137
3138                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3139                             int       cntI = 1;
3140                             int       lastI = 0;
3141
3142                             while (cntI < idxI
3143                                    && AlmostEqual2sComplement(foreY[lastI],
3144                                                               foreY[cntI], 4)
3145                                    && AlmostEqual2sComplement(foreY[lastI],
3146                                                               foreY[cntI + 1],
3147                                                               4)) {
3148                                 cntI++;
3149                             }
3150                             node = gfx_new_area(im->canvas,
3151                                                 backX[0], backY[0],
3152                                                 foreX[0], foreY[0],
3153                                                 foreX[cntI], foreY[cntI],
3154                                                 im->gdes[i].col);
3155                             while (cntI < idxI) {
3156                                 lastI = cntI;
3157                                 cntI++;
3158                                 while (cntI < idxI
3159                                        &&
3160                                        AlmostEqual2sComplement(foreY[lastI],
3161                                                                foreY[cntI], 4)
3162                                        &&
3163                                        AlmostEqual2sComplement(foreY[lastI],
3164                                                                foreY[cntI +
3165                                                                      1], 4)) {
3166                                     cntI++;
3167                                 }
3168                                 gfx_add_point(node, foreX[cntI], foreY[cntI]);
3169                             }
3170                             gfx_add_point(node, backX[idxI], backY[idxI]);
3171                             while (idxI > 1) {
3172                                 lastI = idxI;
3173                                 idxI--;
3174                                 while (idxI > 1
3175                                        &&
3176                                        AlmostEqual2sComplement(backY[lastI],
3177                                                                backY[idxI], 4)
3178                                        &&
3179                                        AlmostEqual2sComplement(backY[lastI],
3180                                                                backY[idxI -
3181                                                                      1], 4)) {
3182                                     idxI--;
3183                                 }
3184                                 gfx_add_point(node, backX[idxI], backY[idxI]);
3185                             }
3186                             idxI = -1;
3187                             drawem = 0;
3188                         }
3189                         if (drawem != 0) {
3190                             drawem = 0;
3191                             idxI = -1;
3192                         }
3193                         if (ii == im->xsize)
3194                             break;
3195
3196                         /* keep things simple for now, just draw these bars
3197                            do not try to build a big and complex area */
3198
3199
3200                         if (im->slopemode == 0 && ii == 0) {
3201                             continue;
3202                         }
3203                         if (isnan(im->gdes[i].p_data[ii])) {
3204                             drawem = 1;
3205                             continue;
3206                         }
3207                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3208                         if (lastgdes && im->gdes[i].stack) {
3209                             ybase = ytr(im, lastgdes->p_data[ii]);
3210                         } else {
3211                             ybase = ytr(im, areazero);
3212                         }
3213                         if (ybase == ytop) {
3214                             drawem = 1;
3215                             continue;
3216                         }
3217                         /* every area has to be wound clock-wise,
3218                            so we have to make sur base remains base  */
3219                         if (ybase > ytop) {
3220                             double    extra = ytop;
3221
3222                             ytop = ybase;
3223                             ybase = extra;
3224                         }
3225                         if (im->slopemode == 0) {
3226                             backY[++idxI] = ybase - 0.2;
3227                             backX[idxI] = ii + im->xorigin - 1;
3228                             foreY[idxI] = ytop + 0.2;
3229                             foreX[idxI] = ii + im->xorigin - 1;
3230                         }
3231                         backY[++idxI] = ybase - 0.2;
3232                         backX[idxI] = ii + im->xorigin;
3233                         foreY[idxI] = ytop + 0.2;
3234                         foreX[idxI] = ii + im->xorigin;
3235                     }
3236                     /* close up any remaining area */
3237                     free(foreY);
3238                     free(foreX);
3239                     free(backY);
3240                     free(backX);
3241                 }       /* else GF_LINE */
3242             }
3243             /* if color != 0x0 */
3244             /* make sure we do not run into trouble when stacking on NaN */
3245             for (ii = 0; ii < im->xsize; ii++) {
3246                 if (isnan(im->gdes[i].p_data[ii])) {
3247                     if (lastgdes && (im->gdes[i].stack)) {
3248                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3249                     } else {
3250                         im->gdes[i].p_data[ii] = areazero;
3251                     }
3252                 }
3253             }
3254             lastgdes = &(im->gdes[i]);
3255             break;
3256 #ifdef WITH_PIECHART
3257         case GF_PART:
3258             if (isnan(im->gdes[i].yrule))   /* fetch variable */
3259                 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
3260
3261             if (finite(im->gdes[i].yrule)) {    /* even the fetched var can be NaN */
3262                 pie_part(im, im->gdes[i].col,
3263                          im->pie_x, im->pie_y, im->piesize * 0.4,
3264                          M_PI * 2.0 * PieStart / 100.0,
3265                          M_PI * 2.0 * (PieStart + im->gdes[i].yrule) / 100.0);
3266                 PieStart += im->gdes[i].yrule;
3267             }
3268             break;
3269 #endif
3270         case GF_STACK:
3271             rrd_set_error
3272                 ("STACK should already be turned into LINE or AREA here");
3273             return -1;
3274             break;
3275
3276         }               /* switch */
3277     }
3278 #ifdef WITH_PIECHART
3279     if (piechart == 2) {
3280         im->draw_x_grid = 0;
3281         im->draw_y_grid = 0;
3282     }
3283 #endif
3284
3285
3286     /* grid_paint also does the text */
3287     if (!(im->extra_flags & ONLY_GRAPH))
3288         grid_paint(im);
3289
3290
3291     if (!(im->extra_flags & ONLY_GRAPH))
3292         axis_paint(im);
3293
3294     /* the RULES are the last thing to paint ... */
3295     for (i = 0; i < im->gdes_c; i++) {
3296
3297         switch (im->gdes[i].gf) {
3298         case GF_HRULE:
3299             if (im->gdes[i].yrule >= im->minval
3300                 && im->gdes[i].yrule <= im->maxval)
3301                 gfx_new_line(im->canvas,
3302                              im->xorigin, ytr(im, im->gdes[i].yrule),
3303                              im->xorigin + im->xsize, ytr(im,
3304                                                           im->gdes[i].yrule),
3305                              1.0, im->gdes[i].col);
3306             break;
3307         case GF_VRULE:
3308             if (im->gdes[i].xrule >= im->start
3309                 && im->gdes[i].xrule <= im->end)
3310                 gfx_new_line(im->canvas,
3311                              xtr(im, im->gdes[i].xrule), im->yorigin,
3312                              xtr(im, im->gdes[i].xrule),
3313                              im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3314             break;
3315         default:
3316             break;
3317         }
3318     }
3319
3320
3321     if (strcmp(im->graphfile, "-") == 0) {
3322         fo = im->graphhandle ? im->graphhandle : stdout;
3323 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3324         /* Change translation mode for stdout to BINARY */
3325         _setmode(_fileno(fo), O_BINARY);
3326 #endif
3327     } else {
3328         if ((fo = fopen(im->graphfile, "wb")) == NULL) {
3329             rrd_set_error("Opening '%s' for write: %s", im->graphfile,
3330                           rrd_strerror(errno));
3331             return (-1);
3332         }
3333     }
3334     gfx_render(im->canvas, im->ximg, im->yimg, 0x00000000, fo);
3335     if (strcmp(im->graphfile, "-") != 0)
3336         fclose(fo);
3337     return 0;
3338 }
3339
3340
3341 /*****************************************************
3342  * graph stuff 
3343  *****************************************************/
3344
3345 int gdes_alloc(
3346     image_desc_t *im)
3347 {
3348
3349     im->gdes_c++;
3350     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3351                                                  * sizeof(graph_desc_t))) ==
3352         NULL) {
3353         rrd_set_error("realloc graph_descs");
3354         return -1;
3355     }
3356
3357
3358     im->gdes[im->gdes_c - 1].step = im->step;
3359     im->gdes[im->gdes_c - 1].step_orig = im->step;
3360     im->gdes[im->gdes_c - 1].stack = 0;
3361     im->gdes[im->gdes_c - 1].linewidth = 0;
3362     im->gdes[im->gdes_c - 1].debug = 0;
3363     im->gdes[im->gdes_c - 1].start = im->start;
3364     im->gdes[im->gdes_c - 1].start_orig = im->start;
3365     im->gdes[im->gdes_c - 1].end = im->end;
3366     im->gdes[im->gdes_c - 1].end_orig = im->end;
3367     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3368     im->gdes[im->gdes_c - 1].data = NULL;
3369     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3370     im->gdes[im->gdes_c - 1].data_first = 0;
3371     im->gdes[im->gdes_c - 1].p_data = NULL;
3372     im->gdes[im->gdes_c - 1].rpnp = NULL;
3373     im->gdes[im->gdes_c - 1].shift = 0;
3374     im->gdes[im->gdes_c - 1].col = 0x0;
3375     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3376     im->gdes[im->gdes_c - 1].format[0] = '\0';
3377     im->gdes[im->gdes_c - 1].strftm = 0;
3378     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3379     im->gdes[im->gdes_c - 1].ds = -1;
3380     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3381     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3382     im->gdes[im->gdes_c - 1].p_data = NULL;
3383     im->gdes[im->gdes_c - 1].yrule = DNAN;
3384     im->gdes[im->gdes_c - 1].xrule = 0;
3385     return 0;
3386 }
3387
3388 /* copies input untill the first unescaped colon is found
3389    or until input ends. backslashes have to be escaped as well */
3390 int scan_for_col(
3391     const char *const input,
3392     int len,
3393     char *const output)
3394 {
3395     int       inp, outp = 0;
3396
3397     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3398         if (input[inp] == '\\' &&
3399             input[inp + 1] != '\0' &&
3400             (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3401             output[outp++] = input[++inp];
3402         } else {
3403             output[outp++] = input[inp];
3404         }
3405     }
3406     output[outp] = '\0';
3407     return inp;
3408 }
3409
3410 /* Some surgery done on this function, it became ridiculously big.
3411 ** Things moved:
3412 ** - initializing     now in rrd_graph_init()
3413 ** - options parsing  now in rrd_graph_options()
3414 ** - script parsing   now in rrd_graph_script()
3415 */
3416 int rrd_graph(
3417     int argc,
3418     char **argv,
3419     char ***prdata,
3420     int *xsize,
3421     int *ysize,
3422     FILE * stream,
3423     double *ymin,
3424     double *ymax)
3425 {
3426     image_desc_t im;
3427
3428     rrd_graph_init(&im);
3429     im.graphhandle = stream;
3430
3431     rrd_graph_options(argc, argv, &im);
3432     if (rrd_test_error()) {
3433         im_free(&im);
3434         return -1;
3435     }
3436
3437     if (strlen(argv[optind]) >= MAXPATH) {
3438         rrd_set_error("filename (including path) too long");
3439         im_free(&im);
3440         return -1;
3441     }
3442     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3443     im.graphfile[MAXPATH - 1] = '\0';
3444
3445     rrd_graph_script(argc, argv, &im, 1);
3446     if (rrd_test_error()) {
3447         im_free(&im);
3448         return -1;
3449     }
3450
3451     /* Everything is now read and the actual work can start */
3452
3453     (*prdata) = NULL;
3454     if (graph_paint(&im, prdata) == -1) {
3455         im_free(&im);
3456         return -1;
3457     }
3458
3459     /* The image is generated and needs to be output.
3460      ** Also, if needed, print a line with information about the image.
3461      */
3462
3463     *xsize = im.ximg;
3464     *ysize = im.yimg;
3465     *ymin = im.minval;
3466     *ymax = im.maxval;
3467     if (im.imginfo) {
3468         char     *filename;
3469
3470         if (!(*prdata)) {
3471             /* maybe prdata is not allocated yet ... lets do it now */
3472             if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3473                 rrd_set_error("malloc imginfo");
3474                 return -1;
3475             };
3476         }
3477         if (((*prdata)[0] =
3478              malloc((strlen(im.imginfo) + 200 +
3479                      strlen(im.graphfile)) * sizeof(char)))
3480             == NULL) {
3481             rrd_set_error("malloc imginfo");
3482             return -1;
3483         }
3484         filename = im.graphfile + strlen(im.graphfile);
3485         while (filename > im.graphfile) {
3486             if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3487                 break;
3488             filename--;
3489         }
3490
3491         sprintf((*prdata)[0], im.imginfo, filename,
3492                 (long) (im.canvas->zoom * im.ximg),
3493                 (long) (im.canvas->zoom * im.yimg));
3494     }
3495     im_free(&im);
3496     return 0;
3497 }
3498
3499 void rrd_graph_init(
3500     image_desc_t *im)
3501 {
3502     unsigned int i;
3503
3504 #ifdef HAVE_TZSET
3505     tzset();
3506 #endif
3507 #ifdef HAVE_SETLOCALE
3508     setlocale(LC_TIME, "");
3509 #ifdef HAVE_MBSTOWCS
3510     setlocale(LC_CTYPE, "");
3511 #endif
3512 #endif
3513     im->yorigin = 0;
3514     im->xorigin = 0;
3515     im->minval = 0;
3516     im->xlab_user.minsec = -1;
3517     im->ximg = 0;
3518     im->yimg = 0;
3519     im->xsize = 400;
3520     im->ysize = 100;
3521     im->step = 0;
3522     im->ylegend[0] = '\0';
3523     im->title[0] = '\0';
3524     im->watermark[0] = '\0';
3525     im->minval = DNAN;
3526     im->maxval = DNAN;
3527     im->unitsexponent = 9999;
3528     im->unitslength = 6;
3529     im->forceleftspace = 0;
3530     im->symbol = ' ';
3531     im->viewfactor = 1.0;
3532     im->extra_flags = 0;
3533     im->rigid = 0;
3534     im->gridfit = 1;
3535     im->imginfo = NULL;
3536     im->lazy = 0;
3537     im->slopemode = 0;
3538     im->logarithmic = 0;
3539     im->ygridstep = DNAN;
3540     im->draw_x_grid = 1;
3541     im->draw_y_grid = 1;
3542     im->base = 1000;
3543     im->prt_c = 0;
3544     im->gdes_c = 0;
3545     im->gdes = NULL;
3546     im->canvas = gfx_new_canvas();
3547     im->grid_dash_on = 1;
3548     im->grid_dash_off = 1;
3549     im->tabwidth = 40.0;
3550
3551     for (i = 0; i < DIM(graph_col); i++)
3552         im->graph_col[i] = graph_col[i];
3553
3554 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3555     {
3556         char     *windir;
3557         char      rrd_win_default_font[1000];
3558
3559         windir = getenv("windir");
3560         /* %windir% is something like D:\windows or C:\winnt */
3561         if (windir != NULL) {
3562             strncpy(rrd_win_default_font, windir, 500);
3563             rrd_win_default_font[500] = '\0';
3564             strcat(rrd_win_default_font, "\\fonts\\");
3565             strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3566             for (i = 0; i < DIM(text_prop); i++) {
3567                 strncpy(text_prop[i].font, rrd_win_default_font,
3568                         sizeof(text_prop[i].font) - 1);
3569                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3570             }
3571         }
3572     }
3573 #endif
3574     {
3575         char     *deffont;
3576
3577         deffont = getenv("RRD_DEFAULT_FONT");
3578         if (deffont != NULL) {
3579             for (i = 0; i < DIM(text_prop); i++) {
3580                 strncpy(text_prop[i].font, deffont,
3581                         sizeof(text_prop[i].font) - 1);
3582                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3583             }
3584         }
3585     }
3586     for (i = 0; i < DIM(text_prop); i++) {
3587         im->text_prop[i].size = text_prop[i].size;
3588         strcpy(im->text_prop[i].font, text_prop[i].font);
3589     }
3590 }
3591
3592 void rrd_graph_options(
3593     int argc,
3594     char *argv[],
3595     image_desc_t *im)
3596 {
3597     int       stroff;
3598     char     *parsetime_error = NULL;
3599     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3600     time_t    start_tmp = 0, end_tmp = 0;
3601     long      long_tmp;
3602     struct rrd_time_value start_tv, end_tv;
3603     gfx_color_t color;
3604
3605     optind = 0;
3606     opterr = 0;         /* initialize getopt */
3607
3608     parsetime("end-24h", &start_tv);
3609     parsetime("now", &end_tv);
3610
3611     /* defines for long options without a short equivalent. should be bytes,
3612        and may not collide with (the ASCII value of) short options */
3613 #define LONGOPT_UNITS_SI 255
3614
3615     while (1) {
3616         static struct option long_options[] = {
3617             {"start", required_argument, 0, 's'},
3618             {"end", required_argument, 0, 'e'},
3619             {"x-grid", required_argument, 0, 'x'},
3620             {"y-grid", required_argument, 0, 'y'},
3621             {"vertical-label", required_argument, 0, 'v'},
3622             {"width", required_argument, 0, 'w'},
3623             {"height", required_argument, 0, 'h'},
3624             {"full-size-mode", no_argument, 0, 'D'},
3625             {"interlaced", no_argument, 0, 'i'},
3626             {"upper-limit", required_argument, 0, 'u'},
3627             {"lower-limit", required_argument, 0, 'l'},
3628             {"rigid", no_argument, 0, 'r'},
3629             {"base", required_argument, 0, 'b'},
3630             {"logarithmic", no_argument, 0, 'o'},
3631             {"color", required_argument, 0, 'c'},
3632             {"font", required_argument, 0, 'n'},
3633             {"title", required_argument, 0, 't'},
3634             {"imginfo", required_argument, 0, 'f'},
3635             {"imgformat", required_argument, 0, 'a'},
3636             {"lazy", no_argument, 0, 'z'},
3637             {"zoom", required_argument, 0, 'm'},
3638             {"no-legend", no_argument, 0, 'g'},
3639             {"force-rules-legend", no_argument, 0, 'F'},
3640             {"only-graph", no_argument, 0, 'j'},
3641             {"alt-y-grid", no_argument, 0, 'Y'},
3642             {"no-minor", no_argument, 0, 'I'},
3643             {"slope-mode", no_argument, 0, 'E'},
3644             {"alt-autoscale", no_argument, 0, 'A'},
3645             {"alt-autoscale-min", no_argument, 0, 'J'},
3646             {"alt-autoscale-max", no_argument, 0, 'M'},
3647             {"no-gridfit", no_argument, 0, 'N'},
3648             {"units-exponent", required_argument, 0, 'X'},
3649             {"units-length", required_argument, 0, 'L'},
3650             {"units", required_argument, 0, LONGOPT_UNITS_SI},
3651             {"step", required_argument, 0, 'S'},
3652             {"tabwidth", required_argument, 0, 'T'},
3653             {"font-render-mode", required_argument, 0, 'R'},
3654             {"font-smoothing-threshold", required_argument, 0, 'B'},
3655             {"watermark", required_argument, 0, 'W'},
3656             {"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 */
3657             {0, 0, 0, 0}
3658         };
3659         int       option_index = 0;
3660         int       opt;
3661         int       col_start, col_end;
3662
3663         opt = getopt_long(argc, argv,
3664                           "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:",
3665                           long_options, &option_index);
3666
3667         if (opt == EOF)
3668             break;
3669
3670         switch (opt) {
3671         case 'I':
3672             im->extra_flags |= NOMINOR;
3673             break;
3674         case 'Y':
3675             im->extra_flags |= ALTYGRID;
3676             break;
3677         case 'A':
3678             im->extra_flags |= ALTAUTOSCALE;
3679             break;
3680         case 'J':
3681             im->extra_flags |= ALTAUTOSCALE_MIN;
3682             break;
3683         case 'M':
3684             im->extra_flags |= ALTAUTOSCALE_MAX;
3685             break;
3686         case 'j':
3687             im->extra_flags |= ONLY_GRAPH;
3688             break;
3689         case 'g':
3690             im->extra_flags |= NOLEGEND;
3691             break;
3692         case 'F':
3693             im->extra_flags |= FORCE_RULES_LEGEND;
3694             break;
3695         case LONGOPT_UNITS_SI:
3696             if (im->extra_flags & FORCE_UNITS) {
3697                 rrd_set_error("--units can only be used once!");
3698                 return;
3699             }
3700             if (strcmp(optarg, "si") == 0)
3701                 im->extra_flags |= FORCE_UNITS_SI;
3702             else {
3703                 rrd_set_error("invalid argument for --units: %s", optarg);
3704                 return;
3705             }
3706             break;
3707         case 'X':
3708             im->unitsexponent = atoi(optarg);
3709             break;
3710         case 'L':
3711             im->unitslength = atoi(optarg);
3712             im->forceleftspace = 1;
3713             break;
3714         case 'T':
3715             im->tabwidth = atof(optarg);
3716             break;
3717         case 'S':
3718             im->step = atoi(optarg);
3719             break;
3720         case 'N':
3721             im->gridfit = 0;
3722             break;
3723         case 's':
3724             if ((parsetime_error = parsetime(optarg, &start_tv))) {
3725                 rrd_set_error("start time: %s", parsetime_error);
3726                 return;
3727             }
3728             break;
3729         case 'e':
3730             if ((parsetime_error = parsetime(optarg, &end_tv))) {
3731                 rrd_set_error("end time: %s", parsetime_error);
3732                 return;
3733             }
3734             break;
3735         case 'x':
3736             if (strcmp(optarg, "none") == 0) {
3737                 im->draw_x_grid = 0;
3738                 break;
3739             };
3740
3741             if (sscanf(optarg,
3742                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3743                        scan_gtm,
3744                        &im->xlab_user.gridst,
3745                        scan_mtm,
3746                        &im->xlab_user.mgridst,
3747                        scan_ltm,
3748                        &im->xlab_user.labst,
3749                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3750                 strncpy(im->xlab_form, optarg + stroff,
3751                         sizeof(im->xlab_form) - 1);
3752                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3753                 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3754                     rrd_set_error("unknown keyword %s", scan_gtm);
3755                     return;
3756                 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3757                            == -1) {
3758                     rrd_set_error("unknown keyword %s", scan_mtm);
3759                     return;
3760                 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3761                            -1) {
3762                     rrd_set_error("unknown keyword %s", scan_ltm);
3763                     return;
3764                 }
3765                 im->xlab_user.minsec = 1;
3766                 im->xlab_user.stst = im->xlab_form;
3767             } else {
3768                 rrd_set_error("invalid x-grid format");
3769                 return;
3770             }
3771             break;
3772         case 'y':
3773
3774             if (strcmp(optarg, "none") == 0) {
3775                 im->draw_y_grid = 0;
3776                 break;
3777             };
3778
3779             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3780                 if (im->ygridstep <= 0) {
3781                     rrd_set_error("grid step must be > 0");
3782                     return;
3783                 } else if (im->ylabfact < 1) {
3784                     rrd_set_error("label factor must be > 0");
3785                     return;
3786                 }
3787             } else {
3788                 rrd_set_error("invalid y-grid format");
3789                 return;
3790             }
3791             break;
3792         case 'v':
3793             strncpy(im->ylegend, optarg, 150);
3794             im->ylegend[150] = '\0';
3795             break;
3796         case 'u':
3797             im->maxval = atof(optarg);
3798             break;
3799         case 'l':
3800             im->minval = atof(optarg);
3801             break;
3802         case 'b':
3803             im->base = atol(optarg);
3804             if (im->base != 1024 && im->base != 1000) {
3805                 rrd_set_error
3806                     ("the only sensible value for base apart from 1000 is 1024");
3807                 return;
3808             }
3809             break;
3810         case 'w':
3811             long_tmp = atol(optarg);
3812             if (long_tmp < 10) {
3813                 rrd_set_error("width below 10 pixels");
3814                 return;
3815             }
3816             im->xsize = long_tmp;
3817             break;
3818         case 'h':
3819             long_tmp = atol(optarg);
3820             if (long_tmp < 10) {
3821                 rrd_set_error("height below 10 pixels");
3822                 return;
3823             }
3824             im->ysize = long_tmp;
3825             break;
3826         case 'D':
3827             im->extra_flags |= FULL_SIZE_MODE;
3828             break;
3829         case 'i':
3830             im->canvas->interlaced = 1;
3831             break;
3832         case 'r':
3833             im->rigid = 1;
3834             break;
3835         case 'f':
3836             im->imginfo = optarg;
3837             break;
3838         case 'a':
3839             if ((int) (im->canvas->imgformat = if_conv(optarg)) == -1) {
3840                 rrd_set_error("unsupported graphics format '%s'", optarg);
3841                 return;
3842             }
3843             break;
3844         case 'z':
3845             im->lazy = 1;
3846             break;
3847         case 'E':
3848             im->slopemode = 1;
3849             break;
3850
3851         case 'o':
3852             im->logarithmic = 1;
3853             break;
3854         case 'c':
3855             if (sscanf(optarg,
3856                        "%10[A-Z]#%n%8lx%n",
3857                        col_nam, &col_start, &color, &col_end) == 2) {
3858                 int       ci;
3859                 int       col_len = col_end - col_start;
3860
3861                 switch (col_len) {
3862                 case 3:
3863                     color = (((color & 0xF00) * 0x110000) |
3864                              ((color & 0x0F0) * 0x011000) |
3865                              ((color & 0x00F) * 0x001100) | 0x000000FF);
3866                     break;
3867                 case 4:
3868                     color = (((color & 0xF000) * 0x11000) |
3869                              ((color & 0x0F00) * 0x01100) |
3870                              ((color & 0x00F0) * 0x00110) |
3871                              ((color & 0x000F) * 0x00011)
3872                         );
3873                     break;
3874                 case 6:
3875                     color = (color << 8) + 0xff /* shift left by 8 */ ;
3876                     break;
3877                 case 8:
3878                     break;
3879                 default:
3880                     rrd_set_error("the color format is #RRGGBB[AA]");
3881                     return;
3882                 }
3883                 if ((ci = grc_conv(col_nam)) != -1) {
3884                     im->graph_col[ci] = color;
3885                 } else {
3886                     rrd_set_error("invalid color name '%s'", col_nam);
3887                     return;
3888                 }
3889             } else {
3890                 rrd_set_error("invalid color def format");
3891                 return;
3892             }
3893             break;
3894         case 'n':{
3895             char      prop[15];
3896             double    size = 1;
3897             char      font[1024] = "";
3898
3899             if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
3900                 int       sindex, propidx;
3901
3902                 if ((sindex = text_prop_conv(prop)) != -1) {
3903                     for (propidx = sindex; propidx < TEXT_PROP_LAST;
3904                          propidx++) {
3905                         if (size > 0) {
3906                             im->text_prop[propidx].size = size;
3907                         }
3908                         if (strlen(font) > 0) {
3909                             strcpy(im->text_prop[propidx].font, font);
3910                         }
3911                         if (propidx == sindex && sindex != 0)
3912                             break;
3913                     }
3914                 } else {
3915                     rrd_set_error("invalid fonttag '%s'", prop);
3916                     return;
3917                 }
3918             } else {
3919                 rrd_set_error("invalid text property format");
3920                 return;
3921             }
3922             break;
3923         }
3924         case 'm':
3925             im->canvas->zoom = atof(optarg);
3926             if (im->canvas->zoom <= 0.0) {
3927                 rrd_set_error("zoom factor must be > 0");
3928                 return;
3929             }
3930             break;
3931         case 't':
3932             strncpy(im->title, optarg, 150);
3933             im->title[150] = '\0';
3934             break;
3935
3936         case 'R':
3937             if (strcmp(optarg, "normal") == 0)
3938                 im->canvas->aa_type = AA_NORMAL;
3939             else if (strcmp(optarg, "light") == 0)
3940                 im->canvas->aa_type = AA_LIGHT;
3941             else if (strcmp(optarg, "mono") == 0)
3942                 im->canvas->aa_type = AA_NONE;
3943             else {
3944                 rrd_set_error("unknown font-render-mode '%s'", optarg);
3945                 return;
3946             }
3947             break;
3948
3949         case 'B':
3950             im->canvas->font_aa_threshold = atof(optarg);
3951             break;
3952
3953         case 'W':
3954             strncpy(im->watermark, optarg, 100);
3955             im->watermark[99] = '\0';
3956             break;
3957
3958         case '?':
3959             if (optopt != 0)
3960                 rrd_set_error("unknown option '%c'", optopt);
3961             else
3962                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
3963             return;
3964         }
3965     }
3966
3967     if (optind >= argc) {
3968         rrd_set_error("missing filename");
3969         return;
3970     }
3971
3972     if (im->logarithmic == 1 && im->minval <= 0) {
3973         rrd_set_error
3974             ("for a logarithmic yaxis you must specify a lower-limit > 0");
3975         return;
3976     }
3977
3978     if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
3979         /* error string is set in parsetime.c */
3980         return;
3981     }
3982
3983     if (start_tmp < 3600 * 24 * 365 * 10) {
3984         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
3985                       start_tmp);
3986         return;
3987     }
3988
3989     if (end_tmp < start_tmp) {
3990         rrd_set_error("start (%ld) should be less than end (%ld)",
3991                       start_tmp, end_tmp);
3992         return;
3993     }
3994
3995     im->start = start_tmp;
3996     im->end = end_tmp;
3997     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
3998 }
3999
4000 int rrd_graph_color(
4001     image_desc_t *im,
4002     char *var,
4003     char *err,
4004     int optional)
4005 {
4006     char     *color;
4007     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4008
4009     color = strstr(var, "#");
4010     if (color == NULL) {
4011         if (optional == 0) {
4012             rrd_set_error("Found no color in %s", err);
4013             return 0;
4014         }
4015         return 0;
4016     } else {
4017         int       n = 0;
4018         char     *rest;
4019         gfx_color_t col;
4020
4021         rest = strstr(color, ":");
4022         if (rest != NULL)
4023             n = rest - color;
4024         else
4025             n = strlen(color);
4026
4027         switch (n) {
4028         case 7:
4029             sscanf(color, "#%6lx%n", &col, &n);
4030             col = (col << 8) + 0xff /* shift left by 8 */ ;
4031             if (n != 7)
4032                 rrd_set_error("Color problem in %s", err);
4033             break;
4034         case 9:
4035             sscanf(color, "#%8lx%n", &col, &n);
4036             if (n == 9)
4037                 break;
4038         default:
4039             rrd_set_error("Color problem in %s", err);
4040         }
4041         if (rrd_test_error())
4042             return 0;
4043         gdp->col = col;
4044         return n;
4045     }
4046 }
4047
4048
4049 int bad_format(
4050     char *fmt)
4051 {
4052     char     *ptr;
4053     int       n = 0;
4054
4055     ptr = fmt;
4056     while (*ptr != '\0')
4057         if (*ptr++ == '%') {
4058
4059             /* line cannot end with percent char */
4060             if (*ptr == '\0')
4061                 return 1;
4062
4063             /* '%s', '%S' and '%%' are allowed */
4064             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4065                 ptr++;
4066
4067             /* %c is allowed (but use only with vdef!) */
4068             else if (*ptr == 'c') {
4069                 ptr++;
4070                 n = 1;
4071             }
4072
4073             /* or else '% 6.2lf' and such are allowed */
4074             else {
4075                 /* optional padding character */
4076                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4077                     ptr++;
4078
4079                 /* This should take care of 'm.n' with all three optional */
4080                 while (*ptr >= '0' && *ptr <= '9')
4081                     ptr++;
4082                 if (*ptr == '.')
4083                     ptr++;
4084                 while (*ptr >= '0' && *ptr <= '9')
4085                     ptr++;
4086
4087                 /* Either 'le', 'lf' or 'lg' must follow here */
4088                 if (*ptr++ != 'l')
4089                     return 1;
4090                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4091                     ptr++;
4092                 else
4093                     return 1;
4094                 n++;
4095             }
4096         }
4097
4098     return (n != 1);
4099 }
4100
4101
4102 int vdef_parse(
4103     gdes,
4104     str)
4105     struct graph_desc_t *gdes;
4106     const char *const str;
4107 {
4108     /* A VDEF currently is either "func" or "param,func"
4109      * so the parsing is rather simple.  Change if needed.
4110      */
4111     double    param;
4112     char      func[30];
4113     int       n;
4114
4115     n = 0;
4116     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4117     if (n == (int) strlen(str)) {   /* matched */
4118         ;
4119     } else {
4120         n = 0;
4121         sscanf(str, "%29[A-Z]%n", func, &n);
4122         if (n == (int) strlen(str)) {   /* matched */
4123             param = DNAN;
4124         } else {
4125             rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4126                           gdes->vname);
4127             return -1;
4128         }
4129     }
4130     if (!strcmp("PERCENT", func))
4131         gdes->vf.op = VDEF_PERCENT;
4132     else if (!strcmp("MAXIMUM", func))
4133         gdes->vf.op = VDEF_MAXIMUM;
4134     else if (!strcmp("AVERAGE", func))
4135         gdes->vf.op = VDEF_AVERAGE;
4136     else if (!strcmp("MINIMUM", func))
4137         gdes->vf.op = VDEF_MINIMUM;
4138     else if (!strcmp("TOTAL", func))
4139         gdes->vf.op = VDEF_TOTAL;
4140     else if (!strcmp("FIRST", func))
4141         gdes->vf.op = VDEF_FIRST;
4142     else if (!strcmp("LAST", func))
4143         gdes->vf.op = VDEF_LAST;
4144     else if (!strcmp("LSLSLOPE", func))
4145         gdes->vf.op = VDEF_LSLSLOPE;
4146     else if (!strcmp("LSLINT", func))
4147         gdes->vf.op = VDEF_LSLINT;
4148     else if (!strcmp("LSLCORREL", func))
4149         gdes->vf.op = VDEF_LSLCORREL;
4150     else {
4151         rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4152                       gdes->vname);
4153         return -1;
4154     };
4155
4156     switch (gdes->vf.op) {
4157     case VDEF_PERCENT:
4158         if (isnan(param)) { /* no parameter given */
4159             rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4160                           func, gdes->vname);
4161             return -1;
4162         };
4163         if (param >= 0.0 && param <= 100.0) {
4164             gdes->vf.param = param;
4165             gdes->vf.val = DNAN;    /* undefined */
4166             gdes->vf.when = 0;  /* undefined */
4167         } else {
4168             rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4169                           gdes->vname);
4170             return -1;
4171         };
4172         break;
4173     case VDEF_MAXIMUM:
4174     case VDEF_AVERAGE:
4175     case VDEF_MINIMUM:
4176     case VDEF_TOTAL:
4177     case VDEF_FIRST:
4178     case VDEF_LAST:
4179     case VDEF_LSLSLOPE:
4180     case VDEF_LSLINT:
4181     case VDEF_LSLCORREL:
4182         if (isnan(param)) {
4183             gdes->vf.param = DNAN;
4184             gdes->vf.val = DNAN;
4185             gdes->vf.when = 0;
4186         } else {
4187             rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4188                           func, gdes->vname);
4189             return -1;
4190         };
4191         break;
4192     };
4193     return 0;
4194 }
4195
4196
4197 int vdef_calc(
4198     im,
4199     gdi)
4200     image_desc_t *im;
4201     int gdi;
4202 {
4203     graph_desc_t *src, *dst;
4204     rrd_value_t *data;
4205     long      step, steps;
4206
4207     dst = &im->gdes[gdi];
4208     src = &im->gdes[dst->vidx];
4209     data = src->data + src->ds;
4210     steps = (src->end - src->start) / src->step;
4211
4212 #if 0
4213     printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4214            src->end, steps);
4215 #endif
4216
4217     switch (dst->vf.op) {
4218     case VDEF_PERCENT:{
4219         rrd_value_t *array;
4220         int       field;
4221
4222
4223         if ((array = malloc(steps * sizeof(double))) == NULL) {
4224             rrd_set_error("malloc VDEV_PERCENT");
4225             return -1;
4226         }
4227         for (step = 0; step < steps; step++) {
4228             array[step] = data[step * src->ds_cnt];
4229         }
4230         qsort(array, step, sizeof(double), vdef_percent_compar);
4231
4232         field = (steps - 1) * dst->vf.param / 100;
4233         dst->vf.val = array[field];
4234         dst->vf.when = 0;   /* no time component */
4235         free(array);
4236 #if 0
4237         for (step = 0; step < steps; step++)
4238             printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4239                    step == field ? '*' : ' ');
4240 #endif
4241     }
4242         break;
4243     case VDEF_MAXIMUM:
4244         step = 0;
4245         while (step != steps && isnan(data[step * src->ds_cnt]))
4246             step++;
4247         if (step == steps) {
4248             dst->vf.val = DNAN;
4249             dst->vf.when = 0;
4250         } else {
4251             dst->vf.val = data[step * src->ds_cnt];
4252             dst->vf.when = src->start + (step + 1) * src->step;
4253         }
4254         while (step != steps) {
4255             if (finite(data[step * src->ds_cnt])) {
4256                 if (data[step * src->ds_cnt] > dst->vf.val) {
4257                     dst->vf.val = data[step * src->ds_cnt];
4258                     dst->vf.when = src->start + (step + 1) * src->step;
4259                 }
4260             }
4261             step++;
4262         }
4263         break;
4264     case VDEF_TOTAL:
4265     case VDEF_AVERAGE:{
4266         int       cnt = 0;
4267         double    sum = 0.0;
4268
4269         for (step = 0; step < steps; step++) {
4270             if (finite(data[step * src->ds_cnt])) {
4271                 sum += data[step * src->ds_cnt];
4272                 cnt++;
4273             };
4274         }
4275         if (cnt) {
4276             if (dst->vf.op == VDEF_TOTAL) {
4277                 dst->vf.val = sum * src->step;
4278                 dst->vf.when = 0;   /* no time component */
4279             } else {
4280                 dst->vf.val = sum / cnt;
4281                 dst->vf.when = 0;   /* no time component */
4282             };
4283         } else {
4284             dst->vf.val = DNAN;
4285             dst->vf.when = 0;
4286         }
4287     }
4288         break;
4289     case VDEF_MINIMUM:
4290         step = 0;
4291         while (step != steps && isnan(data[step * src->ds_cnt]))
4292             step++;
4293         if (step == steps) {
4294             dst->vf.val = DNAN;
4295             dst->vf.when = 0;
4296         } else {
4297             dst->vf.val = data[step * src->ds_cnt];
4298             dst->vf.when = src->start + (step + 1) * src->step;
4299         }
4300         while (step != steps) {
4301             if (finite(data[step * src->ds_cnt])) {
4302                 if (data[step * src->ds_cnt] < dst->vf.val) {
4303                     dst->vf.val = data[step * src->ds_cnt];
4304                     dst->vf.when = src->start + (step + 1) * src->step;
4305                 }
4306             }
4307             step++;
4308         }
4309         break;
4310     case VDEF_FIRST:
4311         /* The time value returned here is one step before the
4312          * actual time value.  This is the start of the first
4313          * non-NaN interval.
4314          */
4315         step = 0;
4316         while (step != steps && isnan(data[step * src->ds_cnt]))
4317             step++;
4318         if (step == steps) {    /* all entries were NaN */
4319             dst->vf.val = DNAN;
4320             dst->vf.when = 0;
4321         } else {
4322             dst->vf.val = data[step * src->ds_cnt];
4323             dst->vf.when = src->start + step * src->step;
4324         }
4325         break;
4326     case VDEF_LAST:
4327         /* The time value returned here is the
4328          * actual time value.  This is the end of the last
4329          * non-NaN interval.
4330          */
4331         step = steps - 1;
4332         while (step >= 0 && isnan(data[step * src->ds_cnt]))
4333             step--;
4334         if (step < 0) { /* all entries were NaN */
4335             dst->vf.val = DNAN;
4336             dst->vf.when = 0;
4337         } else {
4338             dst->vf.val = data[step * src->ds_cnt];
4339             dst->vf.when = src->start + (step + 1) * src->step;
4340         }
4341         break;
4342     case VDEF_LSLSLOPE:
4343     case VDEF_LSLINT:
4344     case VDEF_LSLCORREL:{
4345         /* Bestfit line by linear least squares method */
4346
4347         int       cnt = 0;
4348         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4349
4350         SUMx = 0;
4351         SUMy = 0;
4352         SUMxy = 0;
4353         SUMxx = 0;
4354         SUMyy = 0;
4355
4356         for (step = 0; step < steps; step++) {
4357             if (finite(data[step * src->ds_cnt])) {
4358                 cnt++;
4359                 SUMx += step;
4360                 SUMxx += step * step;
4361                 SUMxy += step * data[step * src->ds_cnt];
4362                 SUMy += data[step * src->ds_cnt];
4363                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4364             };
4365         }
4366
4367         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4368         y_intercept = (SUMy - slope * SUMx) / cnt;
4369         correl =
4370             (SUMxy -
4371              (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4372                                           (SUMx * SUMx) / cnt) * (SUMyy -
4373                                                                   (SUMy *
4374                                                                    SUMy) /
4375                                                                   cnt));
4376
4377         if (cnt) {
4378             if (dst->vf.op == VDEF_LSLSLOPE) {
4379                 dst->vf.val = slope;
4380                 dst->vf.when = 0;
4381             } else if (dst->vf.op == VDEF_LSLINT) {
4382                 dst->vf.val = y_intercept;
4383                 dst->vf.when = 0;
4384             } else if (dst->vf.op == VDEF_LSLCORREL) {
4385                 dst->vf.val = correl;
4386                 dst->vf.when = 0;
4387             };
4388
4389         } else {
4390             dst->vf.val = DNAN;
4391             dst->vf.when = 0;
4392         }
4393     }
4394         break;
4395     }
4396     return 0;
4397 }
4398
4399 /* NaN < -INF < finite_values < INF */
4400 int vdef_percent_compar(
4401     a,
4402     b)
4403     const void *a, *b;
4404 {
4405     /* Equality is not returned; this doesn't hurt except
4406      * (maybe) for a little performance.
4407      */
4408
4409     /* First catch NaN values. They are smallest */
4410     if (isnan(*(double *) a))
4411         return -1;
4412     if (isnan(*(double *) b))
4413         return 1;
4414
4415     /* NaN doesn't reach this part so INF and -INF are extremes.
4416      * The sign from isinf() is compatible with the sign we return
4417      */
4418     if (isinf(*(double *) a))
4419         return isinf(*(double *) a);
4420     if (isinf(*(double *) b))
4421         return isinf(*(double *) b);
4422
4423     /* If we reach this, both values must be finite */
4424     if (*(double *) a < *(double *) b)
4425         return -1;
4426     else
4427         return 1;
4428 }