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