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