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