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