2aba6f92c5d1f6a9612280eb7b4396ce97c35645
[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 = leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size*1.8);
1579
1580         for (i = 0; i < im->gdes_c; i++) {
1581             fill_last = fill;
1582
1583             /* hide legends for rules which are not displayed */
1584
1585             if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1586                 if (im->gdes[i].gf == GF_HRULE &&
1587                     (im->gdes[i].yrule < im->minval
1588                      || im->gdes[i].yrule > im->maxval))
1589                     im->gdes[i].legend[0] = '\0';
1590
1591                 if (im->gdes[i].gf == GF_VRULE &&
1592                     (im->gdes[i].xrule < im->start
1593                      || im->gdes[i].xrule > im->end))
1594                     im->gdes[i].legend[0] = '\0';
1595             }
1596
1597             leg_cc = strlen(im->gdes[i].legend);
1598
1599             /* is there a controle code ant the end of the legend string ? */
1600             /* and it is not a tab \\t */
1601             if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\'
1602                 && im->gdes[i].legend[leg_cc - 1] != 't') {
1603                 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1604                 leg_cc -= 2;
1605                 im->gdes[i].legend[leg_cc] = '\0';
1606             } else {
1607                 prt_fctn = '\0';
1608             }
1609             /* only valid control codes */
1610             if (prt_fctn != 'l' && prt_fctn != 'n' &&   /* a synonym for l */
1611                 prt_fctn != 'r' &&
1612                 prt_fctn != 'j' &&
1613                 prt_fctn != 'c' &&
1614                 prt_fctn != 's' &&
1615                 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1616                 free(legspace);
1617                 rrd_set_error("Unknown control code at the end of '%s\\%c'",
1618                               im->gdes[i].legend, prt_fctn);
1619                 return -1;
1620
1621             }
1622
1623             /* remove exess space */
1624             if (prt_fctn == 'n') {
1625                 prt_fctn = 'l';
1626             }
1627
1628             while (prt_fctn == 'g' &&
1629                    leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1630                 leg_cc--;
1631                 im->gdes[i].legend[leg_cc] = '\0';
1632             }
1633             if (leg_cc != 0) {
1634                 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1635
1636                 if (fill > 0) {
1637                     /* no interleg space if string ends in \g */
1638                     fill += legspace[i];
1639                 }
1640                 fill += gfx_get_text_width(im->canvas, fill + border,
1641                                            im->text_prop[TEXT_PROP_LEGEND].
1642                                            font,
1643                                            im->text_prop[TEXT_PROP_LEGEND].
1644                                            size, im->tabwidth,
1645                                            im->gdes[i].legend, 0);
1646                 leg_c++;
1647             } else {
1648                 legspace[i] = 0;
1649             }
1650             /* who said there was a special tag ... ? */
1651             if (prt_fctn == 'g') {
1652                 prt_fctn = '\0';
1653             }
1654             if (prt_fctn == '\0') {
1655                 if (i == im->gdes_c - 1)
1656                     prt_fctn = 'l';
1657
1658                 /* is it time to place the legends ? */
1659                 if (fill > im->ximg - 2 * border) {
1660                     if (leg_c > 1) {
1661                         /* go back one */
1662                         i--;
1663                         fill = fill_last;
1664                         leg_c--;
1665                         prt_fctn = 'j';
1666                     } else {
1667                         prt_fctn = 'l';
1668                     }
1669
1670                 }
1671             }
1672
1673
1674             if (prt_fctn != '\0') {
1675                 leg_x = border;
1676                 if (leg_c >= 2 && prt_fctn == 'j') {
1677                     glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1678                 } else {
1679                     glue = 0;
1680                 }
1681                 if (prt_fctn == 'c')
1682                     leg_x = (im->ximg - fill) / 2.0;
1683                 if (prt_fctn == 'r')
1684                     leg_x = im->ximg - fill - border;
1685
1686                 for (ii = mark; ii <= i; ii++) {
1687                     if (im->gdes[ii].legend[0] == '\0')
1688                         continue;   /* skip empty legends */
1689                     im->gdes[ii].leg_x = leg_x;
1690                     im->gdes[ii].leg_y = leg_y;
1691                     leg_x +=
1692                         gfx_get_text_width(im->canvas, leg_x,
1693                                            im->text_prop[TEXT_PROP_LEGEND].
1694                                            font,
1695                                            im->text_prop[TEXT_PROP_LEGEND].
1696                                            size, im->tabwidth,
1697                                            im->gdes[ii].legend, 0)
1698                         + legspace[ii]
1699                         + glue;
1700                 }
1701                 leg_y_prev = leg_y;
1702                 if (im->extra_flags & FULL_SIZE_MODE) {
1703                     /* only add y space if there was text on the line */
1704                     if (leg_x > border || prt_fctn == 's')
1705                        leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1706                     if (prt_fctn == 's')
1707                        leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1708                 } else {
1709                     if (leg_x > border || prt_fctn == 's')
1710                        leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1711                     if (prt_fctn == 's')
1712                        leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1713                 }
1714                 fill = 0;
1715                 leg_c = 0;
1716                 mark = ii;
1717             }
1718         }
1719
1720         if (im->extra_flags & FULL_SIZE_MODE) {
1721            if (leg_y != leg_y_prev) {
1722               *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1723               im->yorigin = leg_y - im->text_prop[TEXT_PROP_LEGEND].size*1.8;
1724            }
1725         } else {
1726            im->yimg = leg_y_prev;
1727            /* if we did place some legends we have to add vertical space */
1728            if (leg_y != im->yimg)
1729               im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1730         }
1731         free(legspace);
1732     }
1733     return 0;
1734 }
1735
1736 /* create a grid on the graph. it determines what to do
1737    from the values of xsize, start and end */
1738
1739 /* the xaxis labels are determined from the number of seconds per pixel
1740    in the requested graph */
1741
1742
1743
1744 int calc_horizontal_grid(
1745     image_desc_t *im)
1746 {
1747     double    range;
1748     double    scaledrange;
1749     int       pixel, i;
1750     int       gridind = 0;
1751     int       decimals, fractionals;
1752
1753     im->ygrid_scale.labfact = 2;
1754     range = im->maxval - im->minval;
1755     scaledrange = range / im->magfact;
1756
1757     /* does the scale of this graph make it impossible to put lines
1758        on it? If so, give up. */
1759     if (isnan(scaledrange)) {
1760         return 0;
1761     }
1762
1763     /* find grid spaceing */
1764     pixel = 1;
1765     if (isnan(im->ygridstep)) {
1766         if (im->extra_flags & ALTYGRID) {
1767             /* find the value with max number of digits. Get number of digits */
1768             decimals =
1769                 ceil(log10
1770                      (max(fabs(im->maxval), fabs(im->minval)) *
1771                       im->viewfactor / im->magfact));
1772             if (decimals <= 0)  /* everything is small. make place for zero */
1773                 decimals = 1;
1774
1775             im->ygrid_scale.gridstep =
1776                 pow((double) 10,
1777                     floor(log10(range * im->viewfactor / im->magfact))) /
1778                 im->viewfactor * im->magfact;
1779
1780             if (im->ygrid_scale.gridstep == 0)  /* range is one -> 0.1 is reasonable scale */
1781                 im->ygrid_scale.gridstep = 0.1;
1782             /* should have at least 5 lines but no more then 15 */
1783             if (range / im->ygrid_scale.gridstep < 5)
1784                 im->ygrid_scale.gridstep /= 10;
1785             if (range / im->ygrid_scale.gridstep > 15)
1786                 im->ygrid_scale.gridstep *= 10;
1787             if (range / im->ygrid_scale.gridstep > 5) {
1788                 im->ygrid_scale.labfact = 1;
1789                 if (range / im->ygrid_scale.gridstep > 8)
1790                     im->ygrid_scale.labfact = 2;
1791             } else {
1792                 im->ygrid_scale.gridstep /= 5;
1793                 im->ygrid_scale.labfact = 5;
1794             }
1795             fractionals =
1796                 floor(log10
1797                       (im->ygrid_scale.gridstep *
1798                        (double) im->ygrid_scale.labfact * im->viewfactor /
1799                        im->magfact));
1800             if (fractionals < 0) {  /* small amplitude. */
1801                 int       len = decimals - fractionals + 1;
1802
1803                 if (im->unitslength < len + 2)
1804                     im->unitslength = len + 2;
1805                 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
1806                         -fractionals, (im->symbol != ' ' ? " %c" : ""));
1807             } else {
1808                 int       len = decimals + 1;
1809
1810                 if (im->unitslength < len + 2)
1811                     im->unitslength = len + 2;
1812                 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
1813                         (im->symbol != ' ' ? " %c" : ""));
1814             }
1815         } else {
1816             for (i = 0; ylab[i].grid > 0; i++) {
1817                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1818                 gridind = i;
1819                 if (pixel > 7)
1820                     break;
1821             }
1822
1823             for (i = 0; i < 4; i++) {
1824                 if (pixel * ylab[gridind].lfac[i] >=
1825                     2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1826                     im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1827                     break;
1828                 }
1829             }
1830
1831             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1832         }
1833     } else {
1834         im->ygrid_scale.gridstep = im->ygridstep;
1835         im->ygrid_scale.labfact = im->ylabfact;
1836     }
1837     return 1;
1838 }
1839
1840 int draw_horizontal_grid(
1841     image_desc_t *im)
1842 {
1843     int       i;
1844     double    scaledstep;
1845     char      graph_label[100];
1846     int       nlabels = 0;
1847     double    X0 = im->xorigin;
1848     double    X1 = im->xorigin + im->xsize;
1849
1850     int       sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1851     int       egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1852     double    MaxY;
1853
1854     scaledstep =
1855         im->ygrid_scale.gridstep / (double) im->magfact *
1856         (double) im->viewfactor;
1857     MaxY = scaledstep * (double) egrid;
1858     for (i = sgrid; i <= egrid; i++) {
1859         double    Y0 = ytr(im, im->ygrid_scale.gridstep * i);
1860         double    YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
1861
1862         if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
1863             && floor(Y0 + 0.5) <= im->yorigin) {
1864             /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1865                with the chosen settings. Add a label if required by settings, or if
1866                there is only one label so far and the next grid line is out of bounds. */
1867             if (i % im->ygrid_scale.labfact == 0
1868                 || (nlabels == 1
1869                     && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1870                 if (im->symbol == ' ') {
1871                     if (im->extra_flags & ALTYGRID) {
1872                         sprintf(graph_label, im->ygrid_scale.labfmt,
1873                                 scaledstep * (double) i);
1874                     } else {
1875                         if (MaxY < 10) {
1876                             sprintf(graph_label, "%4.1f",
1877                                     scaledstep * (double) i);
1878                         } else {
1879                             sprintf(graph_label, "%4.0f",
1880                                     scaledstep * (double) i);
1881                         }
1882                     }
1883                 } else {
1884                     char      sisym = (i == 0 ? ' ' : im->symbol);
1885
1886                     if (im->extra_flags & ALTYGRID) {
1887                         sprintf(graph_label, im->ygrid_scale.labfmt,
1888                                 scaledstep * (double) i, sisym);
1889                     } else {
1890                         if (MaxY < 10) {
1891                             sprintf(graph_label, "%4.1f %c",
1892                                     scaledstep * (double) i, sisym);
1893                         } else {
1894                             sprintf(graph_label, "%4.0f %c",
1895                                     scaledstep * (double) i, sisym);
1896                         }
1897                     }
1898                 }
1899                 nlabels++;
1900
1901                 gfx_new_text(im->canvas,
1902                              X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
1903                              im->graph_col[GRC_FONT],
1904                              im->text_prop[TEXT_PROP_AXIS].font,
1905                              im->text_prop[TEXT_PROP_AXIS].size,
1906                              im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1907                              graph_label);
1908                 gfx_new_dashed_line(im->canvas,
1909                                     X0 - 2, Y0,
1910                                     X1 + 2, Y0,
1911                                     MGRIDWIDTH, im->graph_col[GRC_MGRID],
1912                                     im->grid_dash_on, im->grid_dash_off);
1913
1914             } else if (!(im->extra_flags & NOMINOR)) {
1915                 gfx_new_dashed_line(im->canvas,
1916                                     X0 - 1, Y0,
1917                                     X1 + 1, Y0,
1918                                     GRIDWIDTH, im->graph_col[GRC_GRID],
1919                                     im->grid_dash_on, im->grid_dash_off);
1920
1921             }
1922         }
1923     }
1924     return 1;
1925 }
1926
1927 /* this is frexp for base 10 */
1928 double    frexp10(
1929     double,
1930     double *);
1931 double frexp10(
1932     double x,
1933     double *e)
1934 {
1935     double    mnt;
1936     int       iexp;
1937
1938     iexp = floor(log(fabs(x)) / log(10));
1939     mnt = x / pow(10.0, iexp);
1940     if (mnt >= 10.0) {
1941         iexp++;
1942         mnt = x / pow(10.0, iexp);
1943     }
1944     *e = iexp;
1945     return mnt;
1946 }
1947
1948 static int AlmostEqual2sComplement(
1949     float A,
1950     float B,
1951     int maxUlps)
1952 {
1953
1954     int       aInt = *(int *) &A;
1955     int       bInt = *(int *) &B;
1956     int       intDiff;
1957
1958     /* Make sure maxUlps is non-negative and small enough that the
1959        default NAN won't compare as equal to anything.  */
1960
1961     /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1962
1963     /* Make aInt lexicographically ordered as a twos-complement int */
1964
1965     if (aInt < 0)
1966         aInt = 0x80000000l - aInt;
1967
1968     /* Make bInt lexicographically ordered as a twos-complement int */
1969
1970     if (bInt < 0)
1971         bInt = 0x80000000l - bInt;
1972
1973     intDiff = abs(aInt - bInt);
1974
1975     if (intDiff <= maxUlps)
1976         return 1;
1977
1978     return 0;
1979 }
1980
1981 /* logaritmic horizontal grid */
1982 int horizontal_log_grid(
1983     image_desc_t *im)
1984 {
1985     double    yloglab[][10] = {
1986         {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1987         {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1988         {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1989         {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1990         {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1991         {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}  /* last line */
1992     };
1993
1994     int       i, j, val_exp, min_exp;
1995     double    nex;      /* number of decades in data */
1996     double    logscale; /* scale in logarithmic space */
1997     int       exfrac = 1;   /* decade spacing */
1998     int       mid = -1; /* row in yloglab for major grid */
1999     double    mspac;    /* smallest major grid spacing (pixels) */
2000     int       flab;     /* first value in yloglab to use */
2001     double    value, tmp, pre_value;
2002     double    X0, X1, Y0;
2003     char      graph_label[100];
2004
2005     nex = log10(im->maxval / im->minval);
2006     logscale = im->ysize / nex;
2007
2008     /* major spacing for data with high dynamic range */
2009     while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2010         if (exfrac == 1)
2011             exfrac = 3;
2012         else
2013             exfrac += 3;
2014     }
2015
2016     /* major spacing for less dynamic data */
2017     do {
2018         /* search best row in yloglab */
2019         mid++;
2020         for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2021         mspac = logscale * log10(10.0 / yloglab[mid][i]);
2022     } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
2023              && yloglab[mid][0] > 0);
2024     if (mid)
2025         mid--;
2026
2027     /* find first value in yloglab */
2028     for (flab = 0;
2029          yloglab[mid][flab] < 10
2030          && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2031     if (yloglab[mid][flab] == 10.0) {
2032         tmp += 1.0;
2033         flab = 0;
2034     }
2035     val_exp = tmp;
2036     if (val_exp % exfrac)
2037         val_exp += abs(-val_exp % exfrac);
2038
2039     X0 = im->xorigin;
2040     X1 = im->xorigin + im->xsize;
2041
2042     /* draw grid */
2043     pre_value = DNAN;
2044     while (1) {
2045
2046         value = yloglab[mid][flab] * pow(10.0, val_exp);
2047         if (AlmostEqual2sComplement(value, pre_value, 4))
2048             break;      /* it seems we are not converging */
2049
2050         pre_value = value;
2051
2052         Y0 = ytr(im, value);
2053         if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2054             break;
2055
2056         /* major grid line */
2057         gfx_new_dashed_line(im->canvas,
2058                             X0 - 2, Y0,
2059                             X1 + 2, Y0,
2060                             MGRIDWIDTH, im->graph_col[GRC_MGRID],
2061                             im->grid_dash_on, im->grid_dash_off);
2062
2063         /* label */
2064         if (im->extra_flags & FORCE_UNITS_SI) {
2065             int       scale;
2066             double    pvalue;
2067             char      symbol;
2068
2069             scale = floor(val_exp / 3.0);
2070             if (value >= 1.0)
2071                 pvalue = pow(10.0, val_exp % 3);
2072             else
2073                 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2074             pvalue *= yloglab[mid][flab];
2075
2076             if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
2077                 ((scale + si_symbcenter) >= 0))
2078                 symbol = si_symbol[scale + si_symbcenter];
2079             else
2080                 symbol = '?';
2081
2082             sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2083         } else
2084             sprintf(graph_label, "%3.0e", value);
2085         gfx_new_text(im->canvas,
2086                      X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
2087                      im->graph_col[GRC_FONT],
2088                      im->text_prop[TEXT_PROP_AXIS].font,
2089                      im->text_prop[TEXT_PROP_AXIS].size,
2090                      im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
2091                      graph_label);
2092
2093         /* minor grid */
2094         if (mid < 4 && exfrac == 1) {
2095             /* find first and last minor line behind current major line
2096              * i is the first line and j tha last */
2097             if (flab == 0) {
2098                 min_exp = val_exp - 1;
2099                 for (i = 1; yloglab[mid][i] < 10.0; i++);
2100                 i = yloglab[mid][i - 1] + 1;
2101                 j = 10;
2102             } else {
2103                 min_exp = val_exp;
2104                 i = yloglab[mid][flab - 1] + 1;
2105                 j = yloglab[mid][flab];
2106             }
2107
2108             /* draw minor lines below current major line */
2109             for (; i < j; i++) {
2110
2111                 value = i * pow(10.0, min_exp);
2112                 if (value < im->minval)
2113                     continue;
2114
2115                 Y0 = ytr(im, value);
2116                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2117                     break;
2118
2119                 /* draw lines */
2120                 gfx_new_dashed_line(im->canvas,
2121                                     X0 - 1, Y0,
2122                                     X1 + 1, Y0,
2123                                     GRIDWIDTH, im->graph_col[GRC_GRID],
2124                                     im->grid_dash_on, im->grid_dash_off);
2125             }
2126         } else if (exfrac > 1) {
2127             for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2128                 value = pow(10.0, i);
2129                 if (value < im->minval)
2130                     continue;
2131
2132                 Y0 = ytr(im, value);
2133                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2134                     break;
2135
2136                 /* draw lines */
2137                 gfx_new_dashed_line(im->canvas,
2138                                     X0 - 1, Y0,
2139                                     X1 + 1, Y0,
2140                                     GRIDWIDTH, im->graph_col[GRC_GRID],
2141                                     im->grid_dash_on, im->grid_dash_off);
2142             }
2143         }
2144
2145         /* next decade */
2146         if (yloglab[mid][++flab] == 10.0) {
2147             flab = 0;
2148             val_exp += exfrac;
2149         }
2150     }
2151
2152     /* draw minor lines after highest major line */
2153     if (mid < 4 && exfrac == 1) {
2154         /* find first and last minor line below current major line
2155          * i is the first line and j tha last */
2156         if (flab == 0) {
2157             min_exp = val_exp - 1;
2158             for (i = 1; yloglab[mid][i] < 10.0; i++);
2159             i = yloglab[mid][i - 1] + 1;
2160             j = 10;
2161         } else {
2162             min_exp = val_exp;
2163             i = yloglab[mid][flab - 1] + 1;
2164             j = yloglab[mid][flab];
2165         }
2166
2167         /* draw minor lines below current major line */
2168         for (; i < j; i++) {
2169
2170             value = i * pow(10.0, min_exp);
2171             if (value < im->minval)
2172                 continue;
2173
2174             Y0 = ytr(im, value);
2175             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2176                 break;
2177
2178             /* draw lines */
2179             gfx_new_dashed_line(im->canvas,
2180                                 X0 - 1, Y0,
2181                                 X1 + 1, Y0,
2182                                 GRIDWIDTH, im->graph_col[GRC_GRID],
2183                                 im->grid_dash_on, im->grid_dash_off);
2184         }
2185     }
2186     /* fancy minor gridlines */
2187     else if (exfrac > 1) {
2188         for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2189             value = pow(10.0, i);
2190             if (value < im->minval)
2191                 continue;
2192
2193             Y0 = ytr(im, value);
2194             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2195                 break;
2196
2197             /* draw lines */
2198             gfx_new_dashed_line(im->canvas,
2199                                 X0 - 1, Y0,
2200                                 X1 + 1, Y0,
2201                                 GRIDWIDTH, im->graph_col[GRC_GRID],
2202                                 im->grid_dash_on, im->grid_dash_off);
2203         }
2204     }
2205
2206     return 1;
2207 }
2208
2209
2210 void vertical_grid(
2211     image_desc_t *im)
2212 {
2213     int       xlab_sel; /* which sort of label and grid ? */
2214     time_t    ti, tilab, timajor;
2215     long      factor;
2216     char      graph_label[100];
2217     double    X0, Y0, Y1;   /* points for filled graph and more */
2218     struct tm tm;
2219
2220     /* the type of time grid is determined by finding
2221        the number of seconds per pixel in the graph */
2222
2223
2224     if (im->xlab_user.minsec == -1) {
2225         factor = (im->end - im->start) / im->xsize;
2226         xlab_sel = 0;
2227         while (xlab[xlab_sel + 1].minsec != -1
2228                && xlab[xlab_sel + 1].minsec <= factor) {
2229             xlab_sel++;
2230         }               /* pick the last one */
2231         while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
2232                && xlab[xlab_sel].length > (im->end - im->start)) {
2233             xlab_sel--;
2234         }               /* go back to the smallest size */
2235         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2236         im->xlab_user.gridst = xlab[xlab_sel].gridst;
2237         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2238         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2239         im->xlab_user.labtm = xlab[xlab_sel].labtm;
2240         im->xlab_user.labst = xlab[xlab_sel].labst;
2241         im->xlab_user.precis = xlab[xlab_sel].precis;
2242         im->xlab_user.stst = xlab[xlab_sel].stst;
2243     }
2244
2245     /* y coords are the same for every line ... */
2246     Y0 = im->yorigin;
2247     Y1 = im->yorigin - im->ysize;
2248
2249
2250     /* paint the minor grid */
2251     if (!(im->extra_flags & NOMINOR)) {
2252         for (ti = find_first_time(im->start,
2253                                   im->xlab_user.gridtm,
2254                                   im->xlab_user.gridst),
2255              timajor = find_first_time(im->start,
2256                                        im->xlab_user.mgridtm,
2257                                        im->xlab_user.mgridst);
2258              ti < im->end;
2259              ti =
2260              find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2261             ) {
2262             /* are we inside the graph ? */
2263             if (ti < im->start || ti > im->end)
2264                 continue;
2265             while (timajor < ti) {
2266                 timajor = find_next_time(timajor,
2267                                          im->xlab_user.mgridtm,
2268                                          im->xlab_user.mgridst);
2269             }
2270             if (ti == timajor)
2271                 continue;   /* skip as falls on major grid line */
2272             X0 = xtr(im, ti);
2273             gfx_new_dashed_line(im->canvas, X0, Y0 + 1, X0, Y1 - 1, GRIDWIDTH,
2274                                 im->graph_col[GRC_GRID],
2275                                 im->grid_dash_on, im->grid_dash_off);
2276
2277         }
2278     }
2279
2280     /* paint the major grid */
2281     for (ti = find_first_time(im->start,
2282                               im->xlab_user.mgridtm,
2283                               im->xlab_user.mgridst);
2284          ti < im->end;
2285          ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2286         ) {
2287         /* are we inside the graph ? */
2288         if (ti < im->start || ti > im->end)
2289             continue;
2290         X0 = xtr(im, ti);
2291         gfx_new_dashed_line(im->canvas, X0, Y0 + 3, X0, Y1 - 2, MGRIDWIDTH,
2292                             im->graph_col[GRC_MGRID],
2293                             im->grid_dash_on, im->grid_dash_off);
2294
2295     }
2296     /* paint the labels below the graph */
2297     for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
2298                               im->xlab_user.labtm,
2299                               im->xlab_user.labst);
2300          ti <= im->end - im->xlab_user.precis / 2;
2301          ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2302         ) {
2303         tilab = ti + im->xlab_user.precis / 2;  /* correct time for the label */
2304         /* are we inside the graph ? */
2305         if (tilab < im->start || tilab > im->end)
2306             continue;
2307
2308 #if HAVE_STRFTIME
2309         localtime_r(&tilab, &tm);
2310         strftime(graph_label, 99, im->xlab_user.stst, &tm);
2311 #else
2312 # error "your libc has no strftime I guess we'll abort the exercise here."
2313 #endif
2314         gfx_new_text(im->canvas,
2315                      xtr(im, tilab),
2316                      Y0 + im->text_prop[TEXT_PROP_AXIS].size * 1.4 + 5,
2317                      im->graph_col[GRC_FONT],
2318                      im->text_prop[TEXT_PROP_AXIS].font,
2319                      im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
2320                      GFX_H_CENTER, GFX_V_BOTTOM, graph_label);
2321
2322     }
2323
2324 }
2325
2326
2327 void axis_paint(
2328     image_desc_t *im)
2329 {
2330     /* draw x and y axis */
2331     /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2332        im->xorigin+im->xsize,im->yorigin-im->ysize,
2333        GRIDWIDTH, im->graph_col[GRC_AXIS]);
2334
2335        gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2336        im->xorigin+im->xsize,im->yorigin-im->ysize,
2337        GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2338
2339     gfx_new_line(im->canvas, im->xorigin - 4, im->yorigin,
2340                  im->xorigin + im->xsize + 4, im->yorigin,
2341                  MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2342
2343     gfx_new_line(im->canvas, im->xorigin, im->yorigin + 4,
2344                  im->xorigin, im->yorigin - im->ysize - 4,
2345                  MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2346
2347
2348     /* arrow for X and Y axis direction */
2349     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 */
2350                  im->graph_col[GRC_ARROW]);
2351
2352     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 */
2353                  im->graph_col[GRC_ARROW]);
2354
2355 }
2356
2357 void grid_paint(
2358     image_desc_t *im)
2359 {
2360     long      i;
2361     int       res = 0;
2362     double    X0, Y0;   /* points for filled graph and more */
2363     gfx_node_t *node;
2364
2365     /* draw 3d border */
2366     node = gfx_new_area(im->canvas, 0, im->yimg,
2367                         2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2368     gfx_add_point(node, im->ximg - 2, 2);
2369     gfx_add_point(node, im->ximg, 0);
2370     gfx_add_point(node, 0, 0);
2371 /*    gfx_add_point( node , 0,im->yimg ); */
2372
2373     node = gfx_new_area(im->canvas, 2, im->yimg - 2,
2374                         im->ximg - 2, im->yimg - 2,
2375                         im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2376     gfx_add_point(node, im->ximg, 0);
2377     gfx_add_point(node, im->ximg, im->yimg);
2378     gfx_add_point(node, 0, im->yimg);
2379 /*    gfx_add_point( node , 0,im->yimg ); */
2380
2381
2382     if (im->draw_x_grid == 1)
2383         vertical_grid(im);
2384
2385     if (im->draw_y_grid == 1) {
2386         if (im->logarithmic) {
2387             res = horizontal_log_grid(im);
2388         } else {
2389             res = draw_horizontal_grid(im);
2390         }
2391
2392         /* dont draw horizontal grid if there is no min and max val */
2393         if (!res) {
2394             char     *nodata = "No Data found";
2395
2396             gfx_new_text(im->canvas, im->ximg / 2,
2397                          (2 * im->yorigin - im->ysize) / 2,
2398                          im->graph_col[GRC_FONT],
2399                          im->text_prop[TEXT_PROP_AXIS].font,
2400                          im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2401                          0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2402         }
2403     }
2404
2405     /* yaxis unit description */
2406     gfx_new_text(im->canvas,
2407                  10, (im->yorigin - im->ysize / 2),
2408                  im->graph_col[GRC_FONT],
2409                  im->text_prop[TEXT_PROP_UNIT].font,
2410                  im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2411                  RRDGRAPH_YLEGEND_ANGLE,
2412                  GFX_H_LEFT, GFX_V_CENTER, im->ylegend);
2413
2414     /* graph title */
2415     gfx_new_text(im->canvas,
2416                  im->ximg / 2, im->text_prop[TEXT_PROP_TITLE].size * 1.3 + 4,
2417                  im->graph_col[GRC_FONT],
2418                  im->text_prop[TEXT_PROP_TITLE].font,
2419                  im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2420                  GFX_H_CENTER, GFX_V_CENTER, im->title);
2421     /* rrdtool 'logo' */
2422     gfx_new_text(im->canvas,
2423                  im->ximg - 7, 7,
2424                  (im->graph_col[GRC_FONT] & 0xffffff00) | 0x00000044,
2425                  im->text_prop[TEXT_PROP_AXIS].font,
2426                  5.5, im->tabwidth, 270,
2427                  GFX_H_RIGHT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2428
2429     /* graph watermark */
2430     if (im->watermark[0] != '\0') {
2431         gfx_new_text(im->canvas,
2432                      im->ximg / 2, im->yimg - 6,
2433                      (im->graph_col[GRC_FONT] & 0xffffff00) | 0x00000044,
2434                      im->text_prop[TEXT_PROP_AXIS].font,
2435                      5.5, im->tabwidth, 0,
2436                      GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2437     }
2438
2439     /* graph labels */
2440     if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2441         for (i = 0; i < im->gdes_c; i++) {
2442             if (im->gdes[i].legend[0] == '\0')
2443                 continue;
2444
2445             /* im->gdes[i].leg_y is the bottom of the legend */
2446             X0 = im->gdes[i].leg_x;
2447             Y0 = im->gdes[i].leg_y;
2448             gfx_new_text(im->canvas, X0, Y0,
2449                          im->graph_col[GRC_FONT],
2450                          im->text_prop[TEXT_PROP_LEGEND].font,
2451                          im->text_prop[TEXT_PROP_LEGEND].size,
2452                          im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2453                          im->gdes[i].legend);
2454             /* The legend for GRAPH items starts with "M " to have
2455                enough space for the box */
2456             if (im->gdes[i].gf != GF_PRINT &&
2457                 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2458                 int       boxH, boxV;
2459
2460                 boxH = gfx_get_text_width(im->canvas, 0,
2461                                           im->text_prop[TEXT_PROP_LEGEND].
2462                                           font,
2463                                           im->text_prop[TEXT_PROP_LEGEND].
2464                                           size, im->tabwidth, "o", 0) * 1.2;
2465                 boxV = boxH * 1.1;
2466
2467                 /* make sure transparent colors show up the same way as in the graph */
2468                 node = gfx_new_area(im->canvas,
2469                                     X0, Y0 - boxV,
2470                                     X0, Y0,
2471                                     X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2472                 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2473
2474                 node = gfx_new_area(im->canvas,
2475                                     X0, Y0 - boxV,
2476                                     X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2477                 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2478                 node = gfx_new_line(im->canvas,
2479                                     X0, Y0 - boxV,
2480                                     X0, Y0, 1.0, im->graph_col[GRC_FRAME]);
2481                 gfx_add_point(node, X0 + boxH, Y0);
2482                 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2483                 gfx_close_path(node);
2484             }
2485         }
2486     }
2487 }
2488
2489
2490 /*****************************************************
2491  * lazy check make sure we rely need to create this graph
2492  *****************************************************/
2493
2494 int lazy_check(
2495     image_desc_t *im)
2496 {
2497     FILE     *fd = NULL;
2498     int       size = 1;
2499     struct stat imgstat;
2500
2501     if (im->lazy == 0)
2502         return 0;       /* no lazy option */
2503     if (stat(im->graphfile, &imgstat) != 0)
2504         return 0;       /* can't stat */
2505     /* one pixel in the existing graph is more then what we would
2506        change here ... */
2507     if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2508         return 0;
2509     if ((fd = fopen(im->graphfile, "rb")) == NULL)
2510         return 0;       /* the file does not exist */
2511     switch (im->canvas->imgformat) {
2512     case IF_PNG:
2513         size = PngSize(fd, &(im->ximg), &(im->yimg));
2514         break;
2515     default:
2516         size = 1;
2517     }
2518     fclose(fd);
2519     return size;
2520 }
2521
2522 #ifdef WITH_PIECHART
2523 void pie_part(
2524     image_desc_t *im,
2525     gfx_color_t color,
2526     double PieCenterX,
2527     double PieCenterY,
2528     double Radius,
2529     double startangle,
2530     double endangle)
2531 {
2532     gfx_node_t *node;
2533     double    angle;
2534     double    step = M_PI / 50; /* Number of iterations for the circle;
2535                                  ** 10 is definitely too low, more than
2536                                  ** 50 seems to be overkill
2537                                  */
2538
2539     /* Strange but true: we have to work clockwise or else
2540      ** anti aliasing nor transparency don't work.
2541      **
2542      ** This test is here to make sure we do it right, also
2543      ** this makes the for...next loop more easy to implement.
2544      ** The return will occur if the user enters a negative number
2545      ** (which shouldn't be done according to the specs) or if the
2546      ** programmers do something wrong (which, as we all know, never
2547      ** happens anyway :)
2548      */
2549     if (endangle < startangle)
2550         return;
2551
2552     /* Hidden feature: Radius decreases each full circle */
2553     angle = startangle;
2554     while (angle >= 2 * M_PI) {
2555         angle -= 2 * M_PI;
2556         Radius *= 0.8;
2557     }
2558
2559     node = gfx_new_area(im->canvas,
2560                         PieCenterX + sin(startangle) * Radius,
2561                         PieCenterY - cos(startangle) * Radius,
2562                         PieCenterX,
2563                         PieCenterY,
2564                         PieCenterX + sin(endangle) * Radius,
2565                         PieCenterY - cos(endangle) * Radius, color);
2566     for (angle = endangle; angle - startangle >= step; angle -= step) {
2567         gfx_add_point(node,
2568                       PieCenterX + sin(angle) * Radius,
2569                       PieCenterY - cos(angle) * Radius);
2570     }
2571 }
2572
2573 #endif
2574
2575 int graph_size_location(
2576     image_desc_t *im,
2577     int elements
2578 #ifdef WITH_PIECHART
2579     ,
2580     int piechart
2581 #endif
2582     )
2583 {
2584     /* The actual size of the image to draw is determined from
2585      ** several sources.  The size given on the command line is
2586      ** the graph area but we need more as we have to draw labels
2587      ** and other things outside the graph area
2588      */
2589
2590     int       Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2591 #ifdef WITH_PIECHART
2592         Xpie = 0, Ypie = 0,
2593 #endif
2594         Yxlabel = 0,
2595 #if 0
2596         Xlegend = 0, Ylegend = 0,
2597 #endif
2598         Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2599
2600     if (im->extra_flags & ONLY_GRAPH) {
2601         im->xorigin = 0;
2602         im->ximg = im->xsize;
2603         im->yimg = im->ysize;
2604         im->yorigin = im->ysize;
2605         ytr(im, DNAN);
2606         return 0;
2607     }
2608
2609     /** +---+--------------------------------------------+
2610      ** | y |...............graph title..................|
2611      ** |   +---+-------------------------------+--------+
2612      ** | a | y |                               |        |
2613      ** | x |   |                               |        |
2614      ** | i | a |                               |  pie   |
2615      ** | s | x |       main graph area         | chart  |
2616      ** |   | i |                               |  area  |
2617      ** | t | s |                               |        |
2618      ** | i |   |                               |        |
2619      ** | t | l |                               |        |
2620      ** | l | b +-------------------------------+--------+
2621      ** | e | l |       x axis labels           |        |
2622      ** +---+---+-------------------------------+--------+
2623      ** |....................legends.....................|
2624      ** +------------------------------------------------+
2625      ** |                   watermark                    |
2626      ** +------------------------------------------------+
2627      */
2628     
2629     if (im->ylegend[0] != '\0' ) {
2630          Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2631     }
2632
2633     if (im->title[0] != '\0') {
2634          /* The title is placed "inbetween" two text lines so it
2635          ** automatically has some vertical spacing.  The horizontal
2636          ** spacing is added here, on each side.
2637          */
2638          /* if necessary, reduce the font size of the title until it fits the image width */
2639          Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2640     }
2641
2642     if (elements) {
2643          if (im->draw_x_grid) {
2644                Yxlabel=im->text_prop[TEXT_PROP_AXIS].size *2.5;
2645          }
2646          if (im->draw_y_grid || im->forceleftspace ) {
2647                Xylabel=gfx_get_text_width(im->canvas, 0,
2648                             im->text_prop[TEXT_PROP_AXIS].font,
2649                             im->text_prop[TEXT_PROP_AXIS].size,
2650                             im->tabwidth,
2651                             "0", 0) * im->unitslength;
2652          }
2653     }
2654
2655     if (im->extra_flags & FULL_SIZE_MODE) {
2656         /* The actual size of the image to draw has been determined by the user.
2657         ** The graph area is the space remaining after accounting for the legend,
2658         ** the watermark, the pie chart, the axis labels, and the title.
2659         */
2660         im->xorigin =0;
2661         im->ximg = im->xsize;
2662         im->yimg = im->ysize;
2663         im->yorigin = im->ysize;
2664         Xmain=im->ximg;
2665         Ymain=im->yimg;
2666
2667         im->yorigin += Ytitle;
2668
2669 #ifdef WITH_PIECHART
2670         if (piechart) {
2671             im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2672             Xpie=im->piesize;
2673             Ypie=im->piesize;
2674         }
2675 #endif
2676
2677         /* Now calculate the total size.  Insert some spacing where
2678            desired.  im->xorigin and im->yorigin need to correspond
2679            with the lower left corner of the main graph area or, if
2680            this one is not set, the imaginary box surrounding the
2681            pie chart area. */
2682
2683         /* Initial size calculation for the main graph area */
2684         Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2685         if (Xmain) Xmain -= Xspacing; /* put space between main graph area and right edge */
2686
2687 #ifdef WITH_PIECHART
2688         Xmain -= Xpie; /* remove pie width from main graph area */
2689         if (Xpie) Xmain -= Xspacing; /* put space between pie and main graph area */
2690 #endif
2691
2692         im->xorigin = Xspacing + Xylabel;
2693
2694         /* the length of the title should not influence with width of the graph
2695            if (Xtitle > im->ximg) im->ximg = Xtitle; */
2696
2697         if (Xvertical) { /* unit description */
2698             Xmain -= Xvertical;
2699             im->xorigin += Xvertical;
2700         }
2701         im->xsize = Xmain;
2702         xtr(im,0);
2703
2704         /* The vertical size of the image is known in advance.  The main graph area
2705         ** (Ymain) and im->yorigin must be set according to the space requirements
2706         ** of the legend and the axis labels.
2707         */
2708
2709         /* Determine where to place the legends onto the image.
2710         ** Set Ymain and adjust im->yorigin to match the space requirements.
2711         */
2712         if (leg_place(im,&Ymain)==-1)
2713             return -1;
2714
2715 #ifdef WITH_PIECHART
2716         /* if (im->yimg < Ypie) im->yimg = Ypie; * not sure what do about this */
2717 #endif
2718
2719         /* remove title space *or* some padding above the graph from the main graph area */
2720         if (Ytitle) {
2721             Ymain -= Ytitle;
2722         } else {
2723             Ymain -= 1.5*Yspacing;
2724         }
2725
2726         /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2727         if (im->watermark[0] != '\0') {
2728             Ymain -= Ywatermark;
2729         }
2730
2731         im->ysize = Ymain;
2732
2733     } else /* dimension options -width and -height refer to the dimensions of the main graph area */
2734     {
2735         /* The actual size of the image to draw is determined from
2736         ** several sources.  The size given on the command line is
2737         ** the graph area but we need more as we have to draw labels
2738         ** and other things outside the graph area.
2739         */
2740
2741         if (im->ylegend[0] != '\0' ) {
2742                Xvertical = im->text_prop[TEXT_PROP_UNIT].size *2;
2743         }
2744
2745
2746         if (im->title[0] != '\0') {
2747             /* The title is placed "inbetween" two text lines so it
2748             ** automatically has some vertical spacing.  The horizontal
2749             ** spacing is added here, on each side.
2750             */
2751             /* don't care for the with of the title
2752                     Xtitle = gfx_get_text_width(im->canvas, 0,
2753                     im->text_prop[TEXT_PROP_TITLE].font,
2754                     im->text_prop[TEXT_PROP_TITLE].size,
2755                     im->tabwidth,
2756                     im->title, 0) + 2*Xspacing; */
2757             Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2.6+10;
2758         }
2759
2760         if (elements) {
2761             Xmain=im->xsize;
2762             Ymain=im->ysize;
2763         }
2764
2765 #ifdef WITH_PIECHART
2766         if (piechart) {
2767             im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2768             Xpie=im->piesize;
2769             Ypie=im->piesize;
2770         }
2771 #endif
2772
2773         /* Now calculate the total size.  Insert some spacing where
2774            desired.  im->xorigin and im->yorigin need to correspond
2775            with the lower left corner of the main graph area or, if
2776            this one is not set, the imaginary box surrounding the
2777            pie chart area. */
2778
2779         /* The legend width cannot yet be determined, as a result we
2780         ** have problems adjusting the image to it.  For now, we just
2781         ** forget about it at all; the legend will have to fit in the
2782         ** size already allocated.
2783         */
2784         im->ximg = Xylabel + Xmain + 2 * Xspacing;
2785
2786 #ifdef WITH_PIECHART
2787         im->ximg  += Xpie;
2788 #endif
2789
2790         if (Xmain) im->ximg += Xspacing;
2791 #ifdef WITH_PIECHART
2792         if (Xpie) im->ximg += Xspacing;
2793 #endif
2794
2795         im->xorigin = Xspacing + Xylabel;
2796
2797         /* the length of the title should not influence with width of the graph
2798            if (Xtitle > im->ximg) im->ximg = Xtitle; */
2799
2800         if (Xvertical) { /* unit description */
2801             im->ximg += Xvertical;
2802             im->xorigin += Xvertical;
2803         }
2804         xtr(im,0);
2805
2806         /* The vertical size is interesting... we need to compare
2807         ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with 
2808         ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2809         ** in order to start even thinking about Ylegend or Ywatermark.
2810         **
2811         ** Do it in three portions: First calculate the inner part,
2812         ** then do the legend, then adjust the total height of the img,
2813         ** adding space for a watermark if one exists;
2814         */
2815
2816         /* reserve space for main and/or pie */
2817
2818         im->yimg = Ymain + Yxlabel;
2819     
2820 #ifdef WITH_PIECHART
2821         if (im->yimg < Ypie) im->yimg = Ypie;
2822 #endif
2823
2824         im->yorigin = im->yimg - Yxlabel;
2825
2826         /* reserve space for the title *or* some padding above the graph */
2827         if (Ytitle) {
2828             im->yimg += Ytitle;
2829             im->yorigin += Ytitle;
2830         } else {
2831             im->yimg += 1.5*Yspacing;
2832             im->yorigin += 1.5*Yspacing;
2833         }
2834         /* reserve space for padding below the graph */
2835         im->yimg += Yspacing;
2836      
2837         /* Determine where to place the legends onto the image.
2838         ** Adjust im->yimg to match the space requirements.
2839         */
2840         if(leg_place(im,0)==-1)
2841             return -1;
2842         
2843         if (im->watermark[0] != '\0') {
2844             im->yimg += Ywatermark;
2845         }
2846     }
2847
2848 #if 0
2849     if (Xlegend > im->ximg) {
2850         im->ximg = Xlegend;
2851         /* reposition Pie */
2852     }
2853 #endif
2854
2855 #ifdef WITH_PIECHART
2856     /* The pie is placed in the upper right hand corner,
2857     ** just below the title (if any) and with sufficient
2858     ** padding.
2859     */
2860     if (elements) {
2861         im->pie_x = im->ximg - Xspacing - Xpie/2;
2862         im->pie_y = im->yorigin-Ymain+Ypie/2;
2863     } else {
2864         im->pie_x = im->ximg/2;
2865         im->pie_y = im->yorigin-Ypie/2;
2866     }
2867 #endif
2868
2869     ytr(im,DNAN);
2870     return 0;
2871 }
2872
2873 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2874 /* yes we are loosing precision by doing tos with floats instead of doubles
2875    but it seems more stable this way. */
2876
2877
2878 /* draw that picture thing ... */
2879 int graph_paint(
2880     image_desc_t *im,
2881     char ***calcpr)
2882 {
2883     int       i, ii;
2884     int       lazy = lazy_check(im);
2885
2886 #ifdef WITH_PIECHART
2887     int       piechart = 0;
2888     double    PieStart = 0.0;
2889 #endif
2890     FILE     *fo;
2891     gfx_node_t *node;
2892
2893     double    areazero = 0.0;
2894     graph_desc_t *lastgdes = NULL;
2895
2896     /* if we are lazy and there is nothing to PRINT ... quit now */
2897     if (lazy && im->prt_c == 0)
2898         return 0;
2899
2900     /* pull the data from the rrd files ... */
2901
2902     if (data_fetch(im) == -1)
2903         return -1;
2904
2905     /* evaluate VDEF and CDEF operations ... */
2906     if (data_calc(im) == -1)
2907         return -1;
2908
2909 #ifdef WITH_PIECHART
2910     /* check if we need to draw a piechart */
2911     for (i = 0; i < im->gdes_c; i++) {
2912         if (im->gdes[i].gf == GF_PART) {
2913             piechart = 1;
2914             break;
2915         }
2916     }
2917 #endif
2918
2919     /* calculate and PRINT and GPRINT definitions. We have to do it at
2920      * this point because it will affect the length of the legends
2921      * if there are no graph elements we stop here ... 
2922      * if we are lazy, try to quit ... 
2923      */
2924     i = print_calc(im, calcpr);
2925     if (i < 0)
2926         return -1;
2927     if (((i == 0)
2928 #ifdef WITH_PIECHART
2929          && (piechart == 0)
2930 #endif
2931         ) || lazy)
2932         return 0;
2933
2934 #ifdef WITH_PIECHART
2935     /* If there's only the pie chart to draw, signal this */
2936     if (i == 0)
2937         piechart = 2;
2938 #endif
2939
2940 /**************************************************************
2941  *** Calculating sizes and locations became a bit confusing ***
2942  *** so I moved this into a separate function.              ***
2943  **************************************************************/
2944     if (graph_size_location(im, i
2945 #ifdef WITH_PIECHART
2946                             , piechart
2947 #endif
2948         ) == -1)
2949         return -1;
2950
2951     /* get actual drawing data and find min and max values */
2952     if (data_proc(im) == -1)
2953         return -1;
2954
2955     if (!im->logarithmic) {
2956         si_unit(im);
2957     }
2958     /* identify si magnitude Kilo, Mega Giga ? */
2959     if (!im->rigid && !im->logarithmic)
2960         expand_range(im);   /* make sure the upper and lower limit are
2961                                sensible values */
2962
2963     if (!calc_horizontal_grid(im))
2964         return -1;
2965
2966     if (im->gridfit)
2967         apply_gridfit(im);
2968
2969     /* the actual graph is created by going through the individual
2970        graph elements and then drawing them */
2971
2972     node = gfx_new_area(im->canvas,
2973                         0, 0,
2974                         0, im->yimg,
2975                         im->ximg, im->yimg, im->graph_col[GRC_BACK]);
2976
2977     gfx_add_point(node, im->ximg, 0);
2978
2979 #ifdef WITH_PIECHART
2980     if (piechart != 2) {
2981 #endif
2982         node = gfx_new_area(im->canvas,
2983                             im->xorigin, im->yorigin,
2984                             im->xorigin + im->xsize, im->yorigin,
2985                             im->xorigin + im->xsize, im->yorigin - im->ysize,
2986                             im->graph_col[GRC_CANVAS]);
2987
2988         gfx_add_point(node, im->xorigin, im->yorigin - im->ysize);
2989
2990         if (im->minval > 0.0)
2991             areazero = im->minval;
2992         if (im->maxval < 0.0)
2993             areazero = im->maxval;
2994 #ifdef WITH_PIECHART
2995     }
2996 #endif
2997
2998 #ifdef WITH_PIECHART
2999     if (piechart) {
3000         pie_part(im, im->graph_col[GRC_CANVAS], im->pie_x, im->pie_y,
3001                  im->piesize * 0.5, 0, 2 * M_PI);
3002     }
3003 #endif
3004
3005     for (i = 0; i < im->gdes_c; i++) {
3006         switch (im->gdes[i].gf) {
3007         case GF_CDEF:
3008         case GF_VDEF:
3009         case GF_DEF:
3010         case GF_PRINT:
3011         case GF_GPRINT:
3012         case GF_COMMENT:
3013         case GF_HRULE:
3014         case GF_VRULE:
3015         case GF_XPORT:
3016         case GF_SHIFT:
3017             break;
3018         case GF_TICK:
3019             for (ii = 0; ii < im->xsize; ii++) {
3020                 if (!isnan(im->gdes[i].p_data[ii]) &&
3021                     im->gdes[i].p_data[ii] != 0.0) {
3022                     if (im->gdes[i].yrule > 0) {
3023                         gfx_new_line(im->canvas,
3024                                      im->xorigin + ii, im->yorigin,
3025                                      im->xorigin + ii,
3026                                      im->yorigin -
3027                                      im->gdes[i].yrule * im->ysize, 1.0,
3028                                      im->gdes[i].col);
3029                     } else if (im->gdes[i].yrule < 0) {
3030                         gfx_new_line(im->canvas,
3031                                      im->xorigin + ii,
3032                                      im->yorigin - im->ysize,
3033                                      im->xorigin + ii,
3034                                      im->yorigin - (1 -
3035                                                     im->gdes[i].yrule) *
3036                                      im->ysize, 1.0, im->gdes[i].col);
3037
3038                     }
3039                 }
3040             }
3041             break;
3042         case GF_LINE:
3043         case GF_AREA:
3044             /* fix data points at oo and -oo */
3045             for (ii = 0; ii < im->xsize; ii++) {
3046                 if (isinf(im->gdes[i].p_data[ii])) {
3047                     if (im->gdes[i].p_data[ii] > 0) {
3048                         im->gdes[i].p_data[ii] = im->maxval;
3049                     } else {
3050                         im->gdes[i].p_data[ii] = im->minval;
3051                     }
3052
3053                 }
3054             }           /* for */
3055
3056             /* *******************************************************
3057                a           ___. (a,t) 
3058                |   |    ___
3059                ____|   |   |   |
3060                |       |___|
3061                -------|--t-1--t--------------------------------      
3062
3063                if we know the value at time t was a then 
3064                we draw a square from t-1 to t with the value a.
3065
3066                ********************************************************* */
3067             if (im->gdes[i].col != 0x0) {
3068                 /* GF_LINE and friend */
3069                 if (im->gdes[i].gf == GF_LINE) {
3070                     double    last_y = 0.0;
3071
3072                     node = NULL;
3073                     for (ii = 1; ii < im->xsize; ii++) {
3074                         if (isnan(im->gdes[i].p_data[ii])
3075                             || (im->slopemode == 1
3076                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3077                             node = NULL;
3078                             continue;
3079                         }
3080                         if (node == NULL) {
3081                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3082                             if (im->slopemode == 0) {
3083                                 node = gfx_new_line(im->canvas,
3084                                                     ii - 1 + im->xorigin,
3085                                                     last_y, ii + im->xorigin,
3086                                                     last_y,
3087                                                     im->gdes[i].linewidth,
3088                                                     im->gdes[i].col);
3089                             } else {
3090                                 node = gfx_new_line(im->canvas,
3091                                                     ii - 1 + im->xorigin,
3092                                                     ytr(im,
3093                                                         im->gdes[i].
3094                                                         p_data[ii - 1]),
3095                                                     ii + im->xorigin, last_y,
3096                                                     im->gdes[i].linewidth,
3097                                                     im->gdes[i].col);
3098                             }
3099                         } else {
3100                             double    new_y = ytr(im, im->gdes[i].p_data[ii]);
3101
3102                             if (im->slopemode == 0
3103                                 && !AlmostEqual2sComplement(new_y, last_y,
3104                                                             4)) {
3105                                 gfx_add_point(node, ii - 1 + im->xorigin,
3106                                               new_y);
3107                             };
3108                             last_y = new_y;
3109                             gfx_add_point(node, ii + im->xorigin, new_y);
3110                         };
3111
3112                     }
3113                 } else {
3114                     int       idxI = -1;
3115                     double   *foreY = malloc(sizeof(double) * im->xsize * 2);
3116                     double   *foreX = malloc(sizeof(double) * im->xsize * 2);
3117                     double   *backY = malloc(sizeof(double) * im->xsize * 2);
3118                     double   *backX = malloc(sizeof(double) * im->xsize * 2);
3119                     int       drawem = 0;
3120
3121                     for (ii = 0; ii <= im->xsize; ii++) {
3122                         double    ybase, ytop;
3123
3124                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3125                             int       cntI = 1;
3126                             int       lastI = 0;
3127
3128                             while (cntI < idxI
3129                                    && AlmostEqual2sComplement(foreY[lastI],
3130                                                               foreY[cntI], 4)
3131                                    && AlmostEqual2sComplement(foreY[lastI],
3132                                                               foreY[cntI + 1],
3133                                                               4)) {
3134                                 cntI++;
3135                             }
3136                             node = gfx_new_area(im->canvas,
3137                                                 backX[0], backY[0],
3138                                                 foreX[0], foreY[0],
3139                                                 foreX[cntI], foreY[cntI],
3140                                                 im->gdes[i].col);
3141                             while (cntI < idxI) {
3142                                 lastI = cntI;
3143                                 cntI++;
3144                                 while (cntI < idxI
3145                                        &&
3146                                        AlmostEqual2sComplement(foreY[lastI],
3147                                                                foreY[cntI], 4)
3148                                        &&
3149                                        AlmostEqual2sComplement(foreY[lastI],
3150                                                                foreY[cntI +
3151                                                                      1], 4)) {
3152                                     cntI++;
3153                                 }
3154                                 gfx_add_point(node, foreX[cntI], foreY[cntI]);
3155                             }
3156                             gfx_add_point(node, backX[idxI], backY[idxI]);
3157                             while (idxI > 1) {
3158                                 lastI = idxI;
3159                                 idxI--;
3160                                 while (idxI > 1
3161                                        &&
3162                                        AlmostEqual2sComplement(backY[lastI],
3163                                                                backY[idxI], 4)
3164                                        &&
3165                                        AlmostEqual2sComplement(backY[lastI],
3166                                                                backY[idxI -
3167                                                                      1], 4)) {
3168                                     idxI--;
3169                                 }
3170                                 gfx_add_point(node, backX[idxI], backY[idxI]);
3171                             }
3172                             idxI = -1;
3173                             drawem = 0;
3174                         }
3175                         if (drawem != 0) {
3176                             drawem = 0;
3177                             idxI = -1;
3178                         }
3179                         if (ii == im->xsize)
3180                             break;
3181
3182                         /* keep things simple for now, just draw these bars
3183                            do not try to build a big and complex area */
3184
3185
3186                         if (im->slopemode == 0 && ii == 0) {
3187                             continue;
3188                         }
3189                         if (isnan(im->gdes[i].p_data[ii])) {
3190                             drawem = 1;
3191                             continue;
3192                         }
3193                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3194                         if (lastgdes && im->gdes[i].stack) {
3195                             ybase = ytr(im, lastgdes->p_data[ii]);
3196                         } else {
3197                             ybase = ytr(im, areazero);
3198                         }
3199                         if (ybase == ytop) {
3200                             drawem = 1;
3201                             continue;
3202                         }
3203                         /* every area has to be wound clock-wise,
3204                            so we have to make sur base remains base  */
3205                         if (ybase > ytop) {
3206                             double    extra = ytop;
3207
3208                             ytop = ybase;
3209                             ybase = extra;
3210                         }
3211                         if (im->slopemode == 0) {
3212                             backY[++idxI] = ybase - 0.2;
3213                             backX[idxI] = ii + im->xorigin - 1;
3214                             foreY[idxI] = ytop + 0.2;
3215                             foreX[idxI] = ii + im->xorigin - 1;
3216                         }
3217                         backY[++idxI] = ybase - 0.2;
3218                         backX[idxI] = ii + im->xorigin;
3219                         foreY[idxI] = ytop + 0.2;
3220                         foreX[idxI] = ii + im->xorigin;
3221                     }
3222                     /* close up any remaining area */
3223                     free(foreY);
3224                     free(foreX);
3225                     free(backY);
3226                     free(backX);
3227                 }       /* else GF_LINE */
3228             }
3229             /* if color != 0x0 */
3230             /* make sure we do not run into trouble when stacking on NaN */
3231             for (ii = 0; ii < im->xsize; ii++) {
3232                 if (isnan(im->gdes[i].p_data[ii])) {
3233                     if (lastgdes && (im->gdes[i].stack)) {
3234                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3235                     } else {
3236                         im->gdes[i].p_data[ii] = areazero;
3237                     }
3238                 }
3239             }
3240             lastgdes = &(im->gdes[i]);
3241             break;
3242 #ifdef WITH_PIECHART
3243         case GF_PART:
3244             if (isnan(im->gdes[i].yrule))   /* fetch variable */
3245                 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
3246
3247             if (finite(im->gdes[i].yrule)) {    /* even the fetched var can be NaN */
3248                 pie_part(im, im->gdes[i].col,
3249                          im->pie_x, im->pie_y, im->piesize * 0.4,
3250                          M_PI * 2.0 * PieStart / 100.0,
3251                          M_PI * 2.0 * (PieStart + im->gdes[i].yrule) / 100.0);
3252                 PieStart += im->gdes[i].yrule;
3253             }
3254             break;
3255 #endif
3256         case GF_STACK:
3257             rrd_set_error
3258                 ("STACK should already be turned into LINE or AREA here");
3259             return -1;
3260             break;
3261
3262         }               /* switch */
3263     }
3264 #ifdef WITH_PIECHART
3265     if (piechart == 2) {
3266         im->draw_x_grid = 0;
3267         im->draw_y_grid = 0;
3268     }
3269 #endif
3270
3271
3272     /* grid_paint also does the text */
3273     if (!(im->extra_flags & ONLY_GRAPH))
3274         grid_paint(im);
3275
3276
3277     if (!(im->extra_flags & ONLY_GRAPH))
3278         axis_paint(im);
3279
3280     /* the RULES are the last thing to paint ... */
3281     for (i = 0; i < im->gdes_c; i++) {
3282
3283         switch (im->gdes[i].gf) {
3284         case GF_HRULE:
3285             if (im->gdes[i].yrule >= im->minval
3286                 && im->gdes[i].yrule <= im->maxval)
3287                 gfx_new_line(im->canvas,
3288                              im->xorigin, ytr(im, im->gdes[i].yrule),
3289                              im->xorigin + im->xsize, ytr(im,
3290                                                           im->gdes[i].yrule),
3291                              1.0, im->gdes[i].col);
3292             break;
3293         case GF_VRULE:
3294             if (im->gdes[i].xrule >= im->start
3295                 && im->gdes[i].xrule <= im->end)
3296                 gfx_new_line(im->canvas,
3297                              xtr(im, im->gdes[i].xrule), im->yorigin,
3298                              xtr(im, im->gdes[i].xrule),
3299                              im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3300             break;
3301         default:
3302             break;
3303         }
3304     }
3305
3306
3307     if (strcmp(im->graphfile, "-") == 0) {
3308         fo = im->graphhandle ? im->graphhandle : stdout;
3309 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3310         /* Change translation mode for stdout to BINARY */
3311         _setmode(_fileno(fo), O_BINARY);
3312 #endif
3313     } else {
3314         if ((fo = fopen(im->graphfile, "wb")) == NULL) {
3315             rrd_set_error("Opening '%s' for write: %s", im->graphfile,
3316                           rrd_strerror(errno));
3317             return (-1);
3318         }
3319     }
3320     gfx_render(im->canvas, im->ximg, im->yimg, 0x00000000, fo);
3321     if (strcmp(im->graphfile, "-") != 0)
3322         fclose(fo);
3323     return 0;
3324 }
3325
3326
3327 /*****************************************************
3328  * graph stuff 
3329  *****************************************************/
3330
3331 int gdes_alloc(
3332     image_desc_t *im)
3333 {
3334
3335     im->gdes_c++;
3336     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3337                                                  * sizeof(graph_desc_t))) ==
3338         NULL) {
3339         rrd_set_error("realloc graph_descs");
3340         return -1;
3341     }
3342
3343
3344     im->gdes[im->gdes_c - 1].step = im->step;
3345     im->gdes[im->gdes_c - 1].step_orig = im->step;
3346     im->gdes[im->gdes_c - 1].stack = 0;
3347     im->gdes[im->gdes_c - 1].linewidth = 0;
3348     im->gdes[im->gdes_c - 1].debug = 0;
3349     im->gdes[im->gdes_c - 1].start = im->start;
3350     im->gdes[im->gdes_c - 1].start_orig = im->start;
3351     im->gdes[im->gdes_c - 1].end = im->end;
3352     im->gdes[im->gdes_c - 1].end_orig = im->end;
3353     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3354     im->gdes[im->gdes_c - 1].data = NULL;
3355     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3356     im->gdes[im->gdes_c - 1].data_first = 0;
3357     im->gdes[im->gdes_c - 1].p_data = NULL;
3358     im->gdes[im->gdes_c - 1].rpnp = NULL;
3359     im->gdes[im->gdes_c - 1].shift = 0;
3360     im->gdes[im->gdes_c - 1].col = 0x0;
3361     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3362     im->gdes[im->gdes_c - 1].format[0] = '\0';
3363     im->gdes[im->gdes_c - 1].strftm = 0;
3364     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3365     im->gdes[im->gdes_c - 1].ds = -1;
3366     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3367     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3368     im->gdes[im->gdes_c - 1].p_data = NULL;
3369     im->gdes[im->gdes_c - 1].yrule = DNAN;
3370     im->gdes[im->gdes_c - 1].xrule = 0;
3371     return 0;
3372 }
3373
3374 /* copies input untill the first unescaped colon is found
3375    or until input ends. backslashes have to be escaped as well */
3376 int scan_for_col(
3377     const char *const input,
3378     int len,
3379     char *const output)
3380 {
3381     int       inp, outp = 0;
3382
3383     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3384         if (input[inp] == '\\' &&
3385             input[inp + 1] != '\0' &&
3386             (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3387             output[outp++] = input[++inp];
3388         } else {
3389             output[outp++] = input[inp];
3390         }
3391     }
3392     output[outp] = '\0';
3393     return inp;
3394 }
3395
3396 /* Some surgery done on this function, it became ridiculously big.
3397 ** Things moved:
3398 ** - initializing     now in rrd_graph_init()
3399 ** - options parsing  now in rrd_graph_options()
3400 ** - script parsing   now in rrd_graph_script()
3401 */
3402 int rrd_graph(
3403     int argc,
3404     char **argv,
3405     char ***prdata,
3406     int *xsize,
3407     int *ysize,
3408     FILE * stream,
3409     double *ymin,
3410     double *ymax)
3411 {
3412     image_desc_t im;
3413
3414     rrd_graph_init(&im);
3415     im.graphhandle = stream;
3416
3417     rrd_graph_options(argc, argv, &im);
3418     if (rrd_test_error()) {
3419         im_free(&im);
3420         return -1;
3421     }
3422
3423     if (strlen(argv[optind]) >= MAXPATH) {
3424         rrd_set_error("filename (including path) too long");
3425         im_free(&im);
3426         return -1;
3427     }
3428     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3429     im.graphfile[MAXPATH - 1] = '\0';
3430
3431     rrd_graph_script(argc, argv, &im, 1);
3432     if (rrd_test_error()) {
3433         im_free(&im);
3434         return -1;
3435     }
3436
3437     /* Everything is now read and the actual work can start */
3438
3439     (*prdata) = NULL;
3440     if (graph_paint(&im, prdata) == -1) {
3441         im_free(&im);
3442         return -1;
3443     }
3444
3445     /* The image is generated and needs to be output.
3446      ** Also, if needed, print a line with information about the image.
3447      */
3448
3449     *xsize = im.ximg;
3450     *ysize = im.yimg;
3451     *ymin = im.minval;
3452     *ymax = im.maxval;
3453     if (im.imginfo) {
3454         char     *filename;
3455
3456         if (!(*prdata)) {
3457             /* maybe prdata is not allocated yet ... lets do it now */
3458             if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3459                 rrd_set_error("malloc imginfo");
3460                 return -1;
3461             };
3462         }
3463         if (((*prdata)[0] =
3464              malloc((strlen(im.imginfo) + 200 +
3465                      strlen(im.graphfile)) * sizeof(char)))
3466             == NULL) {
3467             rrd_set_error("malloc imginfo");
3468             return -1;
3469         }
3470         filename = im.graphfile + strlen(im.graphfile);
3471         while (filename > im.graphfile) {
3472             if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3473                 break;
3474             filename--;
3475         }
3476
3477         sprintf((*prdata)[0], im.imginfo, filename,
3478                 (long) (im.canvas->zoom * im.ximg),
3479                 (long) (im.canvas->zoom * im.yimg));
3480     }
3481     im_free(&im);
3482     return 0;
3483 }
3484
3485 void rrd_graph_init(
3486     image_desc_t *im)
3487 {
3488     unsigned int i;
3489
3490 #ifdef HAVE_TZSET
3491     tzset();
3492 #endif
3493 #ifdef HAVE_SETLOCALE
3494     setlocale(LC_TIME, "");
3495 #ifdef HAVE_MBSTOWCS
3496     setlocale(LC_CTYPE, "");
3497 #endif
3498 #endif
3499     im->yorigin = 0;
3500     im->xorigin = 0;
3501     im->minval = 0;
3502     im->xlab_user.minsec = -1;
3503     im->ximg = 0;
3504     im->yimg = 0;
3505     im->xsize = 400;
3506     im->ysize = 100;
3507     im->step = 0;
3508     im->ylegend[0] = '\0';
3509     im->title[0] = '\0';
3510     im->watermark[0] = '\0';
3511     im->minval = DNAN;
3512     im->maxval = DNAN;
3513     im->unitsexponent = 9999;
3514     im->unitslength = 6;
3515     im->forceleftspace = 0;
3516     im->symbol = ' ';
3517     im->viewfactor = 1.0;
3518     im->extra_flags = 0;
3519     im->rigid = 0;
3520     im->gridfit = 1;
3521     im->imginfo = NULL;
3522     im->lazy = 0;
3523     im->slopemode = 0;
3524     im->logarithmic = 0;
3525     im->ygridstep = DNAN;
3526     im->draw_x_grid = 1;
3527     im->draw_y_grid = 1;
3528     im->base = 1000;
3529     im->prt_c = 0;
3530     im->gdes_c = 0;
3531     im->gdes = NULL;
3532     im->canvas = gfx_new_canvas();
3533     im->grid_dash_on = 1;
3534     im->grid_dash_off = 1;
3535     im->tabwidth = 40.0;
3536
3537     for (i = 0; i < DIM(graph_col); i++)
3538         im->graph_col[i] = graph_col[i];
3539
3540 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3541     {
3542         char     *windir;
3543         char      rrd_win_default_font[1000];
3544
3545         windir = getenv("windir");
3546         /* %windir% is something like D:\windows or C:\winnt */
3547         if (windir != NULL) {
3548             strncpy(rrd_win_default_font, windir, 500);
3549             rrd_win_default_font[500] = '\0';
3550             strcat(rrd_win_default_font, "\\fonts\\");
3551             strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3552             for (i = 0; i < DIM(text_prop); i++) {
3553                 strncpy(text_prop[i].font, rrd_win_default_font,
3554                         sizeof(text_prop[i].font) - 1);
3555                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3556             }
3557         }
3558     }
3559 #endif
3560     {
3561         char     *deffont;
3562
3563         deffont = getenv("RRD_DEFAULT_FONT");
3564         if (deffont != NULL) {
3565             for (i = 0; i < DIM(text_prop); i++) {
3566                 strncpy(text_prop[i].font, deffont,
3567                         sizeof(text_prop[i].font) - 1);
3568                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3569             }
3570         }
3571     }
3572     for (i = 0; i < DIM(text_prop); i++) {
3573         im->text_prop[i].size = text_prop[i].size;
3574         strcpy(im->text_prop[i].font, text_prop[i].font);
3575     }
3576 }
3577
3578 void rrd_graph_options(
3579     int argc,
3580     char *argv[],
3581     image_desc_t *im)
3582 {
3583     int       stroff;
3584     char     *parsetime_error = NULL;
3585     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3586     time_t    start_tmp = 0, end_tmp = 0;
3587     long      long_tmp;
3588     struct rrd_time_value start_tv, end_tv;
3589     gfx_color_t color;
3590
3591     optind = 0;
3592     opterr = 0;         /* initialize getopt */
3593
3594     parsetime("end-24h", &start_tv);
3595     parsetime("now", &end_tv);
3596
3597     /* defines for long options without a short equivalent. should be bytes,
3598        and may not collide with (the ASCII value of) short options */
3599 #define LONGOPT_UNITS_SI 255
3600
3601     while (1) {
3602         static struct option long_options[] = {
3603             {"start", required_argument, 0, 's'},
3604             {"end", required_argument, 0, 'e'},
3605             {"x-grid", required_argument, 0, 'x'},
3606             {"y-grid", required_argument, 0, 'y'},
3607             {"vertical-label", required_argument, 0, 'v'},
3608             {"width", required_argument, 0, 'w'},
3609             {"height", required_argument, 0, 'h'},
3610             {"full-size-mode",  no_argument, 0, 'D'},
3611             {"interlaced", no_argument, 0, 'i'},
3612             {"upper-limit", required_argument, 0, 'u'},
3613             {"lower-limit", required_argument, 0, 'l'},
3614             {"rigid", no_argument, 0, 'r'},
3615             {"base", required_argument, 0, 'b'},
3616             {"logarithmic", no_argument, 0, 'o'},
3617             {"color", required_argument, 0, 'c'},
3618             {"font", required_argument, 0, 'n'},
3619             {"title", required_argument, 0, 't'},
3620             {"imginfo", required_argument, 0, 'f'},
3621             {"imgformat", required_argument, 0, 'a'},
3622             {"lazy", no_argument, 0, 'z'},
3623             {"zoom", required_argument, 0, 'm'},
3624             {"no-legend", no_argument, 0, 'g'},
3625             {"force-rules-legend", no_argument, 0, 'F'},
3626             {"only-graph", no_argument, 0, 'j'},
3627             {"alt-y-grid", no_argument, 0, 'Y'},
3628             {"no-minor", no_argument, 0, 'I'},
3629             {"slope-mode", no_argument, 0, 'E'},
3630             {"alt-autoscale", no_argument, 0, 'A'},
3631             {"alt-autoscale-min", no_argument, 0, 'J'},
3632             {"alt-autoscale-max", no_argument, 0, 'M'},
3633             {"no-gridfit", no_argument, 0, 'N'},
3634             {"units-exponent", required_argument, 0, 'X'},
3635             {"units-length", required_argument, 0, 'L'},
3636             {"units", required_argument, 0, LONGOPT_UNITS_SI},
3637             {"step", required_argument, 0, 'S'},
3638             {"tabwidth", required_argument, 0, 'T'},
3639             {"font-render-mode", required_argument, 0, 'R'},
3640             {"font-smoothing-threshold", required_argument, 0, 'B'},
3641             {"watermark", required_argument, 0, 'W'},
3642             {"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 */
3643             {0, 0, 0, 0}
3644         };
3645         int       option_index = 0;
3646         int       opt;
3647         int       col_start, col_end;
3648
3649         opt = getopt_long(argc, argv,
3650                           "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:",
3651                           long_options, &option_index);
3652
3653         if (opt == EOF)
3654             break;
3655
3656         switch (opt) {
3657         case 'I':
3658             im->extra_flags |= NOMINOR;
3659             break;
3660         case 'Y':
3661             im->extra_flags |= ALTYGRID;
3662             break;
3663         case 'A':
3664             im->extra_flags |= ALTAUTOSCALE;
3665             break;
3666         case 'J':
3667             im->extra_flags |= ALTAUTOSCALE_MIN;
3668             break;
3669         case 'M':
3670             im->extra_flags |= ALTAUTOSCALE_MAX;
3671             break;
3672         case 'j':
3673             im->extra_flags |= ONLY_GRAPH;
3674             break;
3675         case 'g':
3676             im->extra_flags |= NOLEGEND;
3677             break;
3678         case 'F':
3679             im->extra_flags |= FORCE_RULES_LEGEND;
3680             break;
3681         case LONGOPT_UNITS_SI:
3682             if (im->extra_flags & FORCE_UNITS) {
3683                 rrd_set_error("--units can only be used once!");
3684                 return;
3685             }
3686             if (strcmp(optarg, "si") == 0)
3687                 im->extra_flags |= FORCE_UNITS_SI;
3688             else {
3689                 rrd_set_error("invalid argument for --units: %s", optarg);
3690                 return;
3691             }
3692             break;
3693         case 'X':
3694             im->unitsexponent = atoi(optarg);
3695             break;
3696         case 'L':
3697             im->unitslength = atoi(optarg);
3698             im->forceleftspace = 1;
3699             break;
3700         case 'T':
3701             im->tabwidth = atof(optarg);
3702             break;
3703         case 'S':
3704             im->step = atoi(optarg);
3705             break;
3706         case 'N':
3707             im->gridfit = 0;
3708             break;
3709         case 's':
3710             if ((parsetime_error = parsetime(optarg, &start_tv))) {
3711                 rrd_set_error("start time: %s", parsetime_error);
3712                 return;
3713             }
3714             break;
3715         case 'e':
3716             if ((parsetime_error = parsetime(optarg, &end_tv))) {
3717                 rrd_set_error("end time: %s", parsetime_error);
3718                 return;
3719             }
3720             break;
3721         case 'x':
3722             if (strcmp(optarg, "none") == 0) {
3723                 im->draw_x_grid = 0;
3724                 break;
3725             };
3726
3727             if (sscanf(optarg,
3728                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3729                        scan_gtm,
3730                        &im->xlab_user.gridst,
3731                        scan_mtm,
3732                        &im->xlab_user.mgridst,
3733                        scan_ltm,
3734                        &im->xlab_user.labst,
3735                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3736                 strncpy(im->xlab_form, optarg + stroff,
3737                         sizeof(im->xlab_form) - 1);
3738                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3739                 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3740                     rrd_set_error("unknown keyword %s", scan_gtm);
3741                     return;
3742                 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3743                            == -1) {
3744                     rrd_set_error("unknown keyword %s", scan_mtm);
3745                     return;
3746                 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3747                            -1) {
3748                     rrd_set_error("unknown keyword %s", scan_ltm);
3749                     return;
3750                 }
3751                 im->xlab_user.minsec = 1;
3752                 im->xlab_user.stst = im->xlab_form;
3753             } else {
3754                 rrd_set_error("invalid x-grid format");
3755                 return;
3756             }
3757             break;
3758         case 'y':
3759
3760             if (strcmp(optarg, "none") == 0) {
3761                 im->draw_y_grid = 0;
3762                 break;
3763             };
3764
3765             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3766                 if (im->ygridstep <= 0) {
3767                     rrd_set_error("grid step must be > 0");
3768                     return;
3769                 } else if (im->ylabfact < 1) {
3770                     rrd_set_error("label factor must be > 0");
3771                     return;
3772                 }
3773             } else {
3774                 rrd_set_error("invalid y-grid format");
3775                 return;
3776             }
3777             break;
3778         case 'v':
3779             strncpy(im->ylegend, optarg, 150);
3780             im->ylegend[150] = '\0';
3781             break;
3782         case 'u':
3783             im->maxval = atof(optarg);
3784             break;
3785         case 'l':
3786             im->minval = atof(optarg);
3787             break;
3788         case 'b':
3789             im->base = atol(optarg);
3790             if (im->base != 1024 && im->base != 1000) {
3791                 rrd_set_error
3792                     ("the only sensible value for base apart from 1000 is 1024");
3793                 return;
3794             }
3795             break;
3796         case 'w':
3797             long_tmp = atol(optarg);
3798             if (long_tmp < 10) {
3799                 rrd_set_error("width below 10 pixels");
3800                 return;
3801             }
3802             im->xsize = long_tmp;
3803             break;
3804         case 'h':
3805             long_tmp = atol(optarg);
3806             if (long_tmp < 10) {
3807                 rrd_set_error("height below 10 pixels");
3808                 return;
3809             }
3810             im->ysize = long_tmp;
3811             break;
3812         case 'D':
3813             im->extra_flags |= FULL_SIZE_MODE;
3814             break;
3815         case 'i':
3816             im->canvas->interlaced = 1;
3817             break;
3818         case 'r':
3819             im->rigid = 1;
3820             break;
3821         case 'f':
3822             im->imginfo = optarg;
3823             break;
3824         case 'a':
3825             if ((int) (im->canvas->imgformat = if_conv(optarg)) == -1) {
3826                 rrd_set_error("unsupported graphics format '%s'", optarg);
3827                 return;
3828             }
3829             break;
3830         case 'z':
3831             im->lazy = 1;
3832             break;
3833         case 'E':
3834             im->slopemode = 1;
3835             break;
3836
3837         case 'o':
3838             im->logarithmic = 1;
3839             break;
3840         case 'c':
3841             if (sscanf(optarg,
3842                        "%10[A-Z]#%n%8lx%n",
3843                        col_nam, &col_start, &color, &col_end) == 2) {
3844                 int       ci;
3845                 int       col_len = col_end - col_start;
3846
3847                 switch (col_len) {
3848                 case 3:
3849                     color = (((color & 0xF00) * 0x110000) |
3850                              ((color & 0x0F0) * 0x011000) |
3851                              ((color & 0x00F) * 0x001100) | 0x000000FF);
3852                     break;
3853                 case 4:
3854                     color = (((color & 0xF000) * 0x11000) |
3855                              ((color & 0x0F00) * 0x01100) |
3856                              ((color & 0x00F0) * 0x00110) |
3857                              ((color & 0x000F) * 0x00011)
3858                         );
3859                     break;
3860                 case 6:
3861                     color = (color << 8) + 0xff /* shift left by 8 */ ;
3862                     break;
3863                 case 8:
3864                     break;
3865                 default:
3866                     rrd_set_error("the color format is #RRGGBB[AA]");
3867                     return;
3868                 }
3869                 if ((ci = grc_conv(col_nam)) != -1) {
3870                     im->graph_col[ci] = color;
3871                 } else {
3872                     rrd_set_error("invalid color name '%s'", col_nam);
3873                     return;
3874                 }
3875             } else {
3876                 rrd_set_error("invalid color def format");
3877                 return;
3878             }
3879             break;
3880         case 'n':{
3881             char      prop[15];
3882             double    size = 1;
3883             char      font[1024] = "";
3884
3885             if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
3886                 int       sindex, propidx;
3887
3888                 if ((sindex = text_prop_conv(prop)) != -1) {
3889                     for (propidx = sindex; propidx < TEXT_PROP_LAST;
3890                          propidx++) {
3891                         if (size > 0) {
3892                             im->text_prop[propidx].size = size;
3893                         }
3894                         if (strlen(font) > 0) {
3895                             strcpy(im->text_prop[propidx].font, font);
3896                         }
3897                         if (propidx == sindex && sindex != 0)
3898                             break;
3899                     }
3900                 } else {
3901                     rrd_set_error("invalid fonttag '%s'", prop);
3902                     return;
3903                 }
3904             } else {
3905                 rrd_set_error("invalid text property format");
3906                 return;
3907             }
3908             break;
3909         }
3910         case 'm':
3911             im->canvas->zoom = atof(optarg);
3912             if (im->canvas->zoom <= 0.0) {
3913                 rrd_set_error("zoom factor must be > 0");
3914                 return;
3915             }
3916             break;
3917         case 't':
3918             strncpy(im->title, optarg, 150);
3919             im->title[150] = '\0';
3920             break;
3921
3922         case 'R':
3923             if (strcmp(optarg, "normal") == 0)
3924                 im->canvas->aa_type = AA_NORMAL;
3925             else if (strcmp(optarg, "light") == 0)
3926                 im->canvas->aa_type = AA_LIGHT;
3927             else if (strcmp(optarg, "mono") == 0)
3928                 im->canvas->aa_type = AA_NONE;
3929             else {
3930                 rrd_set_error("unknown font-render-mode '%s'", optarg);
3931                 return;
3932             }
3933             break;
3934
3935         case 'B':
3936             im->canvas->font_aa_threshold = atof(optarg);
3937             break;
3938
3939         case 'W':
3940             strncpy(im->watermark, optarg, 100);
3941             im->watermark[99] = '\0';
3942             break;
3943
3944         case '?':
3945             if (optopt != 0)
3946                 rrd_set_error("unknown option '%c'", optopt);
3947             else
3948                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
3949             return;
3950         }
3951     }
3952
3953     if (optind >= argc) {
3954         rrd_set_error("missing filename");
3955         return;
3956     }
3957
3958     if (im->logarithmic == 1 && im->minval <= 0) {
3959         rrd_set_error
3960             ("for a logarithmic yaxis you must specify a lower-limit > 0");
3961         return;
3962     }
3963
3964     if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
3965         /* error string is set in parsetime.c */
3966         return;
3967     }
3968
3969     if (start_tmp < 3600 * 24 * 365 * 10) {
3970         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
3971                       start_tmp);
3972         return;
3973     }
3974
3975     if (end_tmp < start_tmp) {
3976         rrd_set_error("start (%ld) should be less than end (%ld)",
3977                       start_tmp, end_tmp);
3978         return;
3979     }
3980
3981     im->start = start_tmp;
3982     im->end = end_tmp;
3983     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
3984 }
3985
3986 int rrd_graph_color(
3987     image_desc_t *im,
3988     char *var,
3989     char *err,
3990     int optional)
3991 {
3992     char     *color;
3993     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
3994
3995     color = strstr(var, "#");
3996     if (color == NULL) {
3997         if (optional == 0) {
3998             rrd_set_error("Found no color in %s", err);
3999             return 0;
4000         }
4001         return 0;
4002     } else {
4003         int       n = 0;
4004         char     *rest;
4005         gfx_color_t col;
4006
4007         rest = strstr(color, ":");
4008         if (rest != NULL)
4009             n = rest - color;
4010         else
4011             n = strlen(color);
4012
4013         switch (n) {
4014         case 7:
4015             sscanf(color, "#%6lx%n", &col, &n);
4016             col = (col << 8) + 0xff /* shift left by 8 */ ;
4017             if (n != 7)
4018                 rrd_set_error("Color problem in %s", err);
4019             break;
4020         case 9:
4021             sscanf(color, "#%8lx%n", &col, &n);
4022             if (n == 9)
4023                 break;
4024         default:
4025             rrd_set_error("Color problem in %s", err);
4026         }
4027         if (rrd_test_error())
4028             return 0;
4029         gdp->col = col;
4030         return n;
4031     }
4032 }
4033
4034
4035 int bad_format(
4036     char *fmt)
4037 {
4038     char     *ptr;
4039     int       n = 0;
4040
4041     ptr = fmt;
4042     while (*ptr != '\0')
4043         if (*ptr++ == '%') {
4044
4045             /* line cannot end with percent char */
4046             if (*ptr == '\0')
4047                 return 1;
4048
4049             /* '%s', '%S' and '%%' are allowed */
4050             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4051                 ptr++;
4052
4053             /* %c is allowed (but use only with vdef!) */
4054             else if (*ptr == 'c') {
4055                 ptr++;
4056                 n = 1;
4057             }
4058
4059             /* or else '% 6.2lf' and such are allowed */
4060             else {
4061                 /* optional padding character */
4062                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4063                     ptr++;
4064
4065                 /* This should take care of 'm.n' with all three optional */
4066                 while (*ptr >= '0' && *ptr <= '9')
4067                     ptr++;
4068                 if (*ptr == '.')
4069                     ptr++;
4070                 while (*ptr >= '0' && *ptr <= '9')
4071                     ptr++;
4072
4073                 /* Either 'le', 'lf' or 'lg' must follow here */
4074                 if (*ptr++ != 'l')
4075                     return 1;
4076                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4077                     ptr++;
4078                 else
4079                     return 1;
4080                 n++;
4081             }
4082         }
4083
4084     return (n != 1);
4085 }
4086
4087
4088 int vdef_parse(
4089     gdes,
4090     str)
4091     struct graph_desc_t *gdes;
4092     const char *const str;
4093 {
4094     /* A VDEF currently is either "func" or "param,func"
4095      * so the parsing is rather simple.  Change if needed.
4096      */
4097     double    param;
4098     char      func[30];
4099     int       n;
4100
4101     n = 0;
4102     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4103     if (n == (int) strlen(str)) {   /* matched */
4104         ;
4105     } else {
4106         n = 0;
4107         sscanf(str, "%29[A-Z]%n", func, &n);
4108         if (n == (int) strlen(str)) {   /* matched */
4109             param = DNAN;
4110         } else {
4111             rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4112                           gdes->vname);
4113             return -1;
4114         }
4115     }
4116     if (!strcmp("PERCENT", func))
4117         gdes->vf.op = VDEF_PERCENT;
4118     else if (!strcmp("MAXIMUM", func))
4119         gdes->vf.op = VDEF_MAXIMUM;
4120     else if (!strcmp("AVERAGE", func))
4121         gdes->vf.op = VDEF_AVERAGE;
4122     else if (!strcmp("MINIMUM", func))
4123         gdes->vf.op = VDEF_MINIMUM;
4124     else if (!strcmp("TOTAL", func))
4125         gdes->vf.op = VDEF_TOTAL;
4126     else if (!strcmp("FIRST", func))
4127         gdes->vf.op = VDEF_FIRST;
4128     else if (!strcmp("LAST", func))
4129         gdes->vf.op = VDEF_LAST;
4130     else if (!strcmp("LSLSLOPE", func))
4131         gdes->vf.op = VDEF_LSLSLOPE;
4132     else if (!strcmp("LSLINT", func))
4133         gdes->vf.op = VDEF_LSLINT;
4134     else if (!strcmp("LSLCORREL", func))
4135         gdes->vf.op = VDEF_LSLCORREL;
4136     else {
4137         rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4138                       gdes->vname);
4139         return -1;
4140     };
4141
4142     switch (gdes->vf.op) {
4143     case VDEF_PERCENT:
4144         if (isnan(param)) { /* no parameter given */
4145             rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4146                           func, gdes->vname);
4147             return -1;
4148         };
4149         if (param >= 0.0 && param <= 100.0) {
4150             gdes->vf.param = param;
4151             gdes->vf.val = DNAN;    /* undefined */
4152             gdes->vf.when = 0;  /* undefined */
4153         } else {
4154             rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4155                           gdes->vname);
4156             return -1;
4157         };
4158         break;
4159     case VDEF_MAXIMUM:
4160     case VDEF_AVERAGE:
4161     case VDEF_MINIMUM:
4162     case VDEF_TOTAL:
4163     case VDEF_FIRST:
4164     case VDEF_LAST:
4165     case VDEF_LSLSLOPE:
4166     case VDEF_LSLINT:
4167     case VDEF_LSLCORREL:
4168         if (isnan(param)) {
4169             gdes->vf.param = DNAN;
4170             gdes->vf.val = DNAN;
4171             gdes->vf.when = 0;
4172         } else {
4173             rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4174                           func, gdes->vname);
4175             return -1;
4176         };
4177         break;
4178     };
4179     return 0;
4180 }
4181
4182
4183 int vdef_calc(
4184     im,
4185     gdi)
4186     image_desc_t *im;
4187     int gdi;
4188 {
4189     graph_desc_t *src, *dst;
4190     rrd_value_t *data;
4191     long      step, steps;
4192
4193     dst = &im->gdes[gdi];
4194     src = &im->gdes[dst->vidx];
4195     data = src->data + src->ds;
4196     steps = (src->end - src->start) / src->step;
4197
4198 #if 0
4199     printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4200            src->end, steps);
4201 #endif
4202
4203     switch (dst->vf.op) {
4204     case VDEF_PERCENT:{
4205         rrd_value_t *array;
4206         int       field;
4207
4208
4209         if ((array = malloc(steps * sizeof(double))) == NULL) {
4210             rrd_set_error("malloc VDEV_PERCENT");
4211             return -1;
4212         }
4213         for (step = 0; step < steps; step++) {
4214             array[step] = data[step * src->ds_cnt];
4215         }
4216         qsort(array, step, sizeof(double), vdef_percent_compar);
4217
4218         field = (steps - 1) * dst->vf.param / 100;
4219         dst->vf.val = array[field];
4220         dst->vf.when = 0;   /* no time component */
4221         free(array);
4222 #if 0
4223         for (step = 0; step < steps; step++)
4224             printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4225                    step == field ? '*' : ' ');
4226 #endif
4227     }
4228         break;
4229     case VDEF_MAXIMUM:
4230         step = 0;
4231         while (step != steps && isnan(data[step * src->ds_cnt]))
4232             step++;
4233         if (step == steps) {
4234             dst->vf.val = DNAN;
4235             dst->vf.when = 0;
4236         } else {
4237             dst->vf.val = data[step * src->ds_cnt];
4238             dst->vf.when = src->start + (step + 1) * src->step;
4239         }
4240         while (step != steps) {
4241             if (finite(data[step * src->ds_cnt])) {
4242                 if (data[step * src->ds_cnt] > dst->vf.val) {
4243                     dst->vf.val = data[step * src->ds_cnt];
4244                     dst->vf.when = src->start + (step + 1) * src->step;
4245                 }
4246             }
4247             step++;
4248         }
4249         break;
4250     case VDEF_TOTAL:
4251     case VDEF_AVERAGE:{
4252         int       cnt = 0;
4253         double    sum = 0.0;
4254
4255         for (step = 0; step < steps; step++) {
4256             if (finite(data[step * src->ds_cnt])) {
4257                 sum += data[step * src->ds_cnt];
4258                 cnt++;
4259             };
4260         }
4261         if (cnt) {
4262             if (dst->vf.op == VDEF_TOTAL) {
4263                 dst->vf.val = sum * src->step;
4264                 dst->vf.when = 0;   /* no time component */
4265             } else {
4266                 dst->vf.val = sum / cnt;
4267                 dst->vf.when = 0;   /* no time component */
4268             };
4269         } else {
4270             dst->vf.val = DNAN;
4271             dst->vf.when = 0;
4272         }
4273     }
4274         break;
4275     case VDEF_MINIMUM:
4276         step = 0;
4277         while (step != steps && isnan(data[step * src->ds_cnt]))
4278             step++;
4279         if (step == steps) {
4280             dst->vf.val = DNAN;
4281             dst->vf.when = 0;
4282         } else {
4283             dst->vf.val = data[step * src->ds_cnt];
4284             dst->vf.when = src->start + (step + 1) * src->step;
4285         }
4286         while (step != steps) {
4287             if (finite(data[step * src->ds_cnt])) {
4288                 if (data[step * src->ds_cnt] < dst->vf.val) {
4289                     dst->vf.val = data[step * src->ds_cnt];
4290                     dst->vf.when = src->start + (step + 1) * src->step;
4291                 }
4292             }
4293             step++;
4294         }
4295         break;
4296     case VDEF_FIRST:
4297         /* The time value returned here is one step before the
4298          * actual time value.  This is the start of the first
4299          * non-NaN interval.
4300          */
4301         step = 0;
4302         while (step != steps && isnan(data[step * src->ds_cnt]))
4303             step++;
4304         if (step == steps) {    /* all entries were NaN */
4305             dst->vf.val = DNAN;
4306             dst->vf.when = 0;
4307         } else {
4308             dst->vf.val = data[step * src->ds_cnt];
4309             dst->vf.when = src->start + step * src->step;
4310         }
4311         break;
4312     case VDEF_LAST:
4313         /* The time value returned here is the
4314          * actual time value.  This is the end of the last
4315          * non-NaN interval.
4316          */
4317         step = steps - 1;
4318         while (step >= 0 && isnan(data[step * src->ds_cnt]))
4319             step--;
4320         if (step < 0) { /* all entries were NaN */
4321             dst->vf.val = DNAN;
4322             dst->vf.when = 0;
4323         } else {
4324             dst->vf.val = data[step * src->ds_cnt];
4325             dst->vf.when = src->start + (step + 1) * src->step;
4326         }
4327         break;
4328     case VDEF_LSLSLOPE:
4329     case VDEF_LSLINT:
4330     case VDEF_LSLCORREL:{
4331         /* Bestfit line by linear least squares method */
4332
4333         int       cnt = 0;
4334         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4335
4336         SUMx = 0;
4337         SUMy = 0;
4338         SUMxy = 0;
4339         SUMxx = 0;
4340         SUMyy = 0;
4341
4342         for (step = 0; step < steps; step++) {
4343             if (finite(data[step * src->ds_cnt])) {
4344                 cnt++;
4345                 SUMx += step;
4346                 SUMxx += step * step;
4347                 SUMxy += step * data[step * src->ds_cnt];
4348                 SUMy += data[step * src->ds_cnt];
4349                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4350             };
4351         }
4352
4353         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4354         y_intercept = (SUMy - slope * SUMx) / cnt;
4355         correl =
4356             (SUMxy -
4357              (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4358                                           (SUMx * SUMx) / cnt) * (SUMyy -
4359                                                                   (SUMy *
4360                                                                    SUMy) /
4361                                                                   cnt));
4362
4363         if (cnt) {
4364             if (dst->vf.op == VDEF_LSLSLOPE) {
4365                 dst->vf.val = slope;
4366                 dst->vf.when = 0;
4367             } else if (dst->vf.op == VDEF_LSLINT) {
4368                 dst->vf.val = y_intercept;
4369                 dst->vf.when = 0;
4370             } else if (dst->vf.op == VDEF_LSLCORREL) {
4371                 dst->vf.val = correl;
4372                 dst->vf.when = 0;
4373             };
4374
4375         } else {
4376             dst->vf.val = DNAN;
4377             dst->vf.when = 0;
4378         }
4379     }
4380         break;
4381     }
4382     return 0;
4383 }
4384
4385 /* NaN < -INF < finite_values < INF */
4386 int vdef_percent_compar(
4387     a,
4388     b)
4389     const void *a, *b;
4390 {
4391     /* Equality is not returned; this doesn't hurt except
4392      * (maybe) for a little performance.
4393      */
4394
4395     /* First catch NaN values. They are smallest */
4396     if (isnan(*(double *) a))
4397         return -1;
4398     if (isnan(*(double *) b))
4399         return 1;
4400
4401     /* NaN doesn't reach this part so INF and -INF are extremes.
4402      * The sign from isinf() is compatible with the sign we return
4403      */
4404     if (isinf(*(double *) a))
4405         return isinf(*(double *) a);
4406     if (isinf(*(double *) b))
4407         return isinf(*(double *) b);
4408
4409     /* If we reach this, both values must be finite */
4410     if (*(double *) a < *(double *) b)
4411         return -1;
4412     else
4413         return 1;
4414 }