fixed indentation
[rrdtool.git] / src / rrd_graph.c
1 /****************************************************************************
2  * RRDtool 1.2.23  Copyright by Tobi Oetiker, 1997-2007
3  ****************************************************************************
4  * rrd__graph.c  produce graphs from data in rrdfiles
5  ****************************************************************************/
6
7
8 #include <sys/stat.h>
9
10 #ifdef WIN32
11 #include "strftime.h"
12 #endif
13 #include "rrd_tool.h"
14
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
16 #include <io.h>
17 #include <fcntl.h>
18 #endif
19
20 #ifdef HAVE_TIME_H
21 #include <time.h>
22 #endif
23
24 #ifdef HAVE_LOCALE_H
25 #include <locale.h>
26 #endif
27
28 #include "rrd_graph.h"
29
30 /* some constant definitions */
31
32
33
34 #ifndef RRD_DEFAULT_FONT
35 /* there is special code later to pick Cour.ttf when running on windows */
36 #define RRD_DEFAULT_FONT "DejaVuSansMono-Roman.ttf"
37 #endif
38
39 text_prop_t text_prop[] = {
40     {8.0, RRD_DEFAULT_FONT}
41     ,                   /* default */
42     {9.0, RRD_DEFAULT_FONT}
43     ,                   /* title */
44     {7.0, RRD_DEFAULT_FONT}
45     ,                   /* axis */
46     {8.0, RRD_DEFAULT_FONT}
47     ,                   /* unit */
48     {8.0, RRD_DEFAULT_FONT} /* legend */
49 };
50
51 xlab_t    xlab[] = {
52     {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
53     ,
54     {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
55     ,
56     {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
57     ,
58     {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
59     ,
60     {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
61     ,
62     {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
63     ,
64     {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 4, 0, "%a %H:%M"}
65     ,
66     {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
67     ,
68     {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
69     ,
70     /*{300,             0,   TMT_HOUR,3,    TMT_HOUR,12,   TMT_HOUR,12,    12*3600,"%a %p"},  this looks silly */
71     {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
72     ,
73     {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
74     ,
75     {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
76     ,
77     {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
78     ,
79     {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
80     ,
81     {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
82      "Week %V"}
83     ,
84     {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
85      "%b"}
86     ,
87     {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
88      "%b"}
89     ,
90     {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
91     ,
92     {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
93      365 * 24 * 3600, "%y"}
94     ,
95     {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
96 };
97
98 /* sensible y label intervals ...*/
99
100 ylab_t    ylab[] = {
101     {0.1, {1, 2, 5, 10}
102      }
103     ,
104     {0.2, {1, 5, 10, 20}
105      }
106     ,
107     {0.5, {1, 2, 4, 10}
108      }
109     ,
110     {1.0, {1, 2, 5, 10}
111      }
112     ,
113     {2.0, {1, 5, 10, 20}
114      }
115     ,
116     {5.0, {1, 2, 4, 10}
117      }
118     ,
119     {10.0, {1, 2, 5, 10}
120      }
121     ,
122     {20.0, {1, 5, 10, 20}
123      }
124     ,
125     {50.0, {1, 2, 4, 10}
126      }
127     ,
128     {100.0, {1, 2, 5, 10}
129      }
130     ,
131     {200.0, {1, 5, 10, 20}
132      }
133     ,
134     {500.0, {1, 2, 4, 10}
135      }
136     ,
137     {0.0, {0, 0, 0, 0}
138      }
139 };
140
141
142 gfx_color_t graph_col[] =   /* default colors */
143 {
144     {1.00, 1.00, 1.00, 1.00},   /* canvas     */
145     {0.95, 0.95, 0.95, 1.00},   /* background */
146     {0.81, 0.81, 0.81, 1.00},   /* shade A    */
147     {0.62, 0.62, 0.62, 1.00},   /* shade B    */
148     {0.56, 0.56, 0.56, 0.75},   /* grid       */
149     {0.87, 0.31, 0.31, 0.60},   /* major grid */
150     {0.00, 0.00, 0.00, 1.00},   /* font       */
151     {0.50, 0.12, 0.12, 1.00},   /* arrow      */
152     {0.12, 0.12, 0.12, 1.00},   /* axis       */
153     {0.00, 0.00, 0.00, 1.00}    /* frame      */
154 };
155
156
157 /* #define DEBUG */
158
159 #ifdef DEBUG
160 # define DPRINT(x)    (void)(printf x, printf("\n"))
161 #else
162 # define DPRINT(x)
163 #endif
164
165
166 /* initialize with xtr(im,0); */
167 int xtr(
168     image_desc_t *im,
169     time_t mytime)
170 {
171     static double pixie;
172
173     if (mytime == 0) {
174         pixie = (double) im->xsize / (double) (im->end - im->start);
175         return im->xorigin;
176     }
177     return (int) ((double) im->xorigin + pixie * (mytime - im->start));
178 }
179
180 /* translate data values into y coordinates */
181 double ytr(
182     image_desc_t *im,
183     double value)
184 {
185     static double pixie;
186     double    yval;
187
188     if (isnan(value)) {
189         if (!im->logarithmic)
190             pixie = (double) im->ysize / (im->maxval - im->minval);
191         else
192             pixie =
193                 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
194         yval = im->yorigin;
195     } else if (!im->logarithmic) {
196         yval = im->yorigin - pixie * (value - im->minval);
197     } else {
198         if (value < im->minval) {
199             yval = im->yorigin;
200         } else {
201             yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
202         }
203     }
204     /* make sure we don't return anything too unreasonable. GD lib can
205        get terribly slow when drawing lines outside its scope. This is 
206        especially problematic in connection with the rigid option */
207     if (!im->rigid) {
208         /* keep yval as-is */
209     } else if (yval > im->yorigin) {
210         yval = im->yorigin + 0.00001;
211     } else if (yval < im->yorigin - im->ysize) {
212         yval = im->yorigin - im->ysize - 0.00001;
213     }
214     return yval;
215 }
216
217
218
219 /* conversion function for symbolic entry names */
220
221
222 #define conv_if(VV,VVV) \
223    if (strcmp(#VV, string) == 0) return VVV ;
224
225 enum gf_en gf_conv(
226     char *string)
227 {
228
229     conv_if(PRINT, GF_PRINT);
230     conv_if(GPRINT, GF_GPRINT);
231     conv_if(COMMENT, GF_COMMENT);
232     conv_if(HRULE, GF_HRULE);
233     conv_if(VRULE, GF_VRULE);
234     conv_if(LINE, GF_LINE);
235     conv_if(AREA, GF_AREA);
236     conv_if(STACK, GF_STACK);
237     conv_if(TICK, GF_TICK);
238     conv_if(TEXTALIGN, GF_TEXTALIGN);
239     conv_if(DEF, GF_DEF);
240     conv_if(CDEF, GF_CDEF);
241     conv_if(VDEF, GF_VDEF);
242     conv_if(XPORT, GF_XPORT);
243     conv_if(SHIFT, GF_SHIFT);
244
245     return (-1);
246 }
247
248 enum gfx_if_en if_conv(
249     char *string)
250 {
251
252     conv_if(PNG, IF_PNG);
253     conv_if(SVG, IF_SVG);
254     conv_if(EPS, IF_EPS);
255     conv_if(PDF, IF_PDF);
256
257     return (-1);
258 }
259
260 enum tmt_en tmt_conv(
261     char *string)
262 {
263
264     conv_if(SECOND, TMT_SECOND);
265     conv_if(MINUTE, TMT_MINUTE);
266     conv_if(HOUR, TMT_HOUR);
267     conv_if(DAY, TMT_DAY);
268     conv_if(WEEK, TMT_WEEK);
269     conv_if(MONTH, TMT_MONTH);
270     conv_if(YEAR, TMT_YEAR);
271     return (-1);
272 }
273
274 enum grc_en grc_conv(
275     char *string)
276 {
277
278     conv_if(BACK, GRC_BACK);
279     conv_if(CANVAS, GRC_CANVAS);
280     conv_if(SHADEA, GRC_SHADEA);
281     conv_if(SHADEB, GRC_SHADEB);
282     conv_if(GRID, GRC_GRID);
283     conv_if(MGRID, GRC_MGRID);
284     conv_if(FONT, GRC_FONT);
285     conv_if(ARROW, GRC_ARROW);
286     conv_if(AXIS, GRC_AXIS);
287     conv_if(FRAME, GRC_FRAME);
288
289     return -1;
290 }
291
292 enum text_prop_en text_prop_conv(
293     char *string)
294 {
295
296     conv_if(DEFAULT, TEXT_PROP_DEFAULT);
297     conv_if(TITLE, TEXT_PROP_TITLE);
298     conv_if(AXIS, TEXT_PROP_AXIS);
299     conv_if(UNIT, TEXT_PROP_UNIT);
300     conv_if(LEGEND, TEXT_PROP_LEGEND);
301     return -1;
302 }
303
304
305 #undef conv_if
306
307 int im_free(
308     image_desc_t *im)
309 {
310     unsigned long i, ii;
311     cairo_status_t status;
312
313     if (im == NULL)
314         return 0;
315     for (i = 0; i < (unsigned) im->gdes_c; i++) {
316         if (im->gdes[i].data_first) {
317             /* careful here, because a single pointer can occur several times */
318             free(im->gdes[i].data);
319             if (im->gdes[i].ds_namv) {
320                 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
321                     free(im->gdes[i].ds_namv[ii]);
322                 free(im->gdes[i].ds_namv);
323             }
324         }
325         free(im->gdes[i].p_data);
326         free(im->gdes[i].rpnp);
327     }
328     free(im->gdes);
329     if (im->font_options)
330         cairo_font_options_destroy(im->font_options);
331
332     status = cairo_status(im->cr);
333
334     if (im->cr)
335         cairo_destroy(im->cr);
336     if (im->surface)
337         cairo_surface_destroy(im->surface);
338     if (status)
339         fprintf(stderr, "OOPS: Cairo has issuesm it can't even die: %s\n",
340                 cairo_status_to_string(status));
341
342     return 0;
343 }
344
345 /* find SI magnitude symbol for the given number*/
346 void auto_scale(
347     image_desc_t *im,   /* image description */
348     double *value,
349     char **symb_ptr,
350     double *magfact)
351 {
352
353     char     *symbol[] = { "a", /* 10e-18 Atto */
354         "f",            /* 10e-15 Femto */
355         "p",            /* 10e-12 Pico */
356         "n",            /* 10e-9  Nano */
357         "u",            /* 10e-6  Micro */
358         "m",            /* 10e-3  Milli */
359         " ",            /* Base */
360         "k",            /* 10e3   Kilo */
361         "M",            /* 10e6   Mega */
362         "G",            /* 10e9   Giga */
363         "T",            /* 10e12  Tera */
364         "P",            /* 10e15  Peta */
365         "E"
366     };                  /* 10e18  Exa */
367
368     int       symbcenter = 6;
369     int       sindex;
370
371     if (*value == 0.0 || isnan(*value)) {
372         sindex = 0;
373         *magfact = 1.0;
374     } else {
375         sindex = floor(log(fabs(*value)) / log((double) im->base));
376         *magfact = pow((double) im->base, (double) sindex);
377         (*value) /= (*magfact);
378     }
379     if (sindex <= symbcenter && sindex >= -symbcenter) {
380         (*symb_ptr) = symbol[sindex + symbcenter];
381     } else {
382         (*symb_ptr) = "?";
383     }
384 }
385
386
387 static char si_symbol[] = {
388     'a',                /* 10e-18 Atto */
389     'f',                /* 10e-15 Femto */
390     'p',                /* 10e-12 Pico */
391     'n',                /* 10e-9  Nano */
392     'u',                /* 10e-6  Micro */
393     'm',                /* 10e-3  Milli */
394     ' ',                /* Base */
395     'k',                /* 10e3   Kilo */
396     'M',                /* 10e6   Mega */
397     'G',                /* 10e9   Giga */
398     'T',                /* 10e12  Tera */
399     'P',                /* 10e15  Peta */
400     'E',                /* 10e18  Exa */
401 };
402 static const int si_symbcenter = 6;
403
404 /* find SI magnitude symbol for the numbers on the y-axis*/
405 void si_unit(
406     image_desc_t *im    /* image description */
407     )
408 {
409
410     double    digits, viewdigits = 0;
411
412     digits =
413         floor(log(max(fabs(im->minval), fabs(im->maxval))) /
414               log((double) im->base));
415
416     if (im->unitsexponent != 9999) {
417         /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
418         viewdigits = floor(im->unitsexponent / 3);
419     } else {
420         viewdigits = digits;
421     }
422
423     im->magfact = pow((double) im->base, digits);
424
425 #ifdef DEBUG
426     printf("digits %6.3f  im->magfact %6.3f\n", digits, im->magfact);
427 #endif
428
429     im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
430
431     if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
432         ((viewdigits + si_symbcenter) >= 0))
433         im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
434     else
435         im->symbol = '?';
436 }
437
438 /*  move min and max values around to become sensible */
439
440 void expand_range(
441     image_desc_t *im)
442 {
443     double    sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
444         600.0, 500.0, 400.0, 300.0, 250.0,
445         200.0, 125.0, 100.0, 90.0, 80.0,
446         75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
447         25.0, 20.0, 10.0, 9.0, 8.0,
448         7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
449         2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
450         0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
451     };
452
453     double    scaled_min, scaled_max;
454     double    adj;
455     int       i;
456
457
458
459 #ifdef DEBUG
460     printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
461            im->minval, im->maxval, im->magfact);
462 #endif
463
464     if (isnan(im->ygridstep)) {
465         if (im->extra_flags & ALTAUTOSCALE) {
466             /* measure the amplitude of the function. Make sure that
467                graph boundaries are slightly higher then max/min vals
468                so we can see amplitude on the graph */
469             double    delt, fact;
470
471             delt = im->maxval - im->minval;
472             adj = delt * 0.1;
473             fact = 2.0 * pow(10.0,
474                              floor(log10
475                                    (max(fabs(im->minval), fabs(im->maxval)) /
476                                     im->magfact)) - 2);
477             if (delt < fact) {
478                 adj = (fact - delt) * 0.55;
479 #ifdef DEBUG
480                 printf
481                     ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
482                      im->minval, im->maxval, delt, fact, adj);
483 #endif
484             }
485             im->minval -= adj;
486             im->maxval += adj;
487         } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
488             /* measure the amplitude of the function. Make sure that
489                graph boundaries are slightly lower than min vals
490                so we can see amplitude on the graph */
491             adj = (im->maxval - im->minval) * 0.1;
492             im->minval -= adj;
493         } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
494             /* measure the amplitude of the function. Make sure that
495                graph boundaries are slightly higher than max vals
496                so we can see amplitude on the graph */
497             adj = (im->maxval - im->minval) * 0.1;
498             im->maxval += adj;
499         } else {
500             scaled_min = im->minval / im->magfact;
501             scaled_max = im->maxval / im->magfact;
502
503             for (i = 1; sensiblevalues[i] > 0; i++) {
504                 if (sensiblevalues[i - 1] >= scaled_min &&
505                     sensiblevalues[i] <= scaled_min)
506                     im->minval = sensiblevalues[i] * (im->magfact);
507
508                 if (-sensiblevalues[i - 1] <= scaled_min &&
509                     -sensiblevalues[i] >= scaled_min)
510                     im->minval = -sensiblevalues[i - 1] * (im->magfact);
511
512                 if (sensiblevalues[i - 1] >= scaled_max &&
513                     sensiblevalues[i] <= scaled_max)
514                     im->maxval = sensiblevalues[i - 1] * (im->magfact);
515
516                 if (-sensiblevalues[i - 1] <= scaled_max &&
517                     -sensiblevalues[i] >= scaled_max)
518                     im->maxval = -sensiblevalues[i] * (im->magfact);
519             }
520         }
521     } else {
522         /* adjust min and max to the grid definition if there is one */
523         im->minval = (double) im->ylabfact * im->ygridstep *
524             floor(im->minval / ((double) im->ylabfact * im->ygridstep));
525         im->maxval = (double) im->ylabfact * im->ygridstep *
526             ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
527     }
528
529 #ifdef DEBUG
530     fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
531             im->minval, im->maxval, im->magfact);
532 #endif
533 }
534
535
536 void apply_gridfit(
537     image_desc_t *im)
538 {
539     if (isnan(im->minval) || isnan(im->maxval))
540         return;
541     ytr(im, DNAN);
542     if (im->logarithmic) {
543         double    ya, yb, ypix, ypixfrac;
544         double    log10_range = log10(im->maxval) - log10(im->minval);
545
546         ya = pow((double) 10, floor(log10(im->minval)));
547         while (ya < im->minval)
548             ya *= 10;
549         if (ya > im->maxval)
550             return;     /* don't have y=10^x gridline */
551         yb = ya * 10;
552         if (yb <= im->maxval) {
553             /* we have at least 2 y=10^x gridlines.
554                Make sure distance between them in pixels
555                are an integer by expanding im->maxval */
556             double    y_pixel_delta = ytr(im, ya) - ytr(im, yb);
557             double    factor = y_pixel_delta / floor(y_pixel_delta);
558             double    new_log10_range = factor * log10_range;
559             double    new_ymax_log10 = log10(im->minval) + new_log10_range;
560
561             im->maxval = pow(10, new_ymax_log10);
562             ytr(im, DNAN);  /* reset precalc */
563             log10_range = log10(im->maxval) - log10(im->minval);
564         }
565         /* make sure first y=10^x gridline is located on 
566            integer pixel position by moving scale slightly 
567            downwards (sub-pixel movement) */
568         ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
569         ypixfrac = ypix - floor(ypix);
570         if (ypixfrac > 0 && ypixfrac < 1) {
571             double    yfrac = ypixfrac / im->ysize;
572
573             im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
574             im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
575             ytr(im, DNAN);  /* reset precalc */
576         }
577     } else {
578         /* Make sure we have an integer pixel distance between
579            each minor gridline */
580         double    ypos1 = ytr(im, im->minval);
581         double    ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
582         double    y_pixel_delta = ypos1 - ypos2;
583         double    factor = y_pixel_delta / floor(y_pixel_delta);
584         double    new_range = factor * (im->maxval - im->minval);
585         double    gridstep = im->ygrid_scale.gridstep;
586         double    minor_y, minor_y_px, minor_y_px_frac;
587
588         if (im->maxval > 0.0)
589             im->maxval = im->minval + new_range;
590         else
591             im->minval = im->maxval - new_range;
592         ytr(im, DNAN);  /* reset precalc */
593         /* make sure first minor gridline is on integer pixel y coord */
594         minor_y = gridstep * floor(im->minval / gridstep);
595         while (minor_y < im->minval)
596             minor_y += gridstep;
597         minor_y_px = ytr(im, minor_y) + im->ysize;  /* ensure > 0 by adding ysize */
598         minor_y_px_frac = minor_y_px - floor(minor_y_px);
599         if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
600             double    yfrac = minor_y_px_frac / im->ysize;
601             double    range = im->maxval - im->minval;
602
603             im->minval = im->minval - yfrac * range;
604             im->maxval = im->maxval - yfrac * range;
605             ytr(im, DNAN);  /* reset precalc */
606         }
607         calc_horizontal_grid(im);   /* recalc with changed im->maxval */
608     }
609 }
610
611 /* reduce data reimplementation by Alex */
612
613 void reduce_data(
614     enum cf_en cf,      /* which consolidation function ? */
615     unsigned long cur_step, /* step the data currently is in */
616     time_t *start,      /* start, end and step as requested ... */
617     time_t *end,        /* ... by the application will be   ... */
618     unsigned long *step,    /* ... adjusted to represent reality    */
619     unsigned long *ds_cnt,  /* number of data sources in file */
620     rrd_value_t **data)
621 {                       /* two dimensional array containing the data */
622     int       i, reduce_factor = ceil((double) (*step) / (double) cur_step);
623     unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
624         0;
625     rrd_value_t *srcptr, *dstptr;
626
627     (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
628     dstptr = *data;
629     srcptr = *data;
630     row_cnt = ((*end) - (*start)) / cur_step;
631
632 #ifdef DEBUG
633 #define DEBUG_REDUCE
634 #endif
635 #ifdef DEBUG_REDUCE
636     printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
637            row_cnt, reduce_factor, *start, *end, cur_step);
638     for (col = 0; col < row_cnt; col++) {
639         printf("time %10lu: ", *start + (col + 1) * cur_step);
640         for (i = 0; i < *ds_cnt; i++)
641             printf(" %8.2e", srcptr[*ds_cnt * col + i]);
642         printf("\n");
643     }
644 #endif
645
646     /* We have to combine [reduce_factor] rows of the source
647      ** into one row for the destination.  Doing this we also
648      ** need to take care to combine the correct rows.  First
649      ** alter the start and end time so that they are multiples
650      ** of the new step time.  We cannot reduce the amount of
651      ** time so we have to move the end towards the future and
652      ** the start towards the past.
653      */
654     end_offset = (*end) % (*step);
655     start_offset = (*start) % (*step);
656
657     /* If there is a start offset (which cannot be more than
658      ** one destination row), skip the appropriate number of
659      ** source rows and one destination row.  The appropriate
660      ** number is what we do know (start_offset/cur_step) of
661      ** the new interval (*step/cur_step aka reduce_factor).
662      */
663 #ifdef DEBUG_REDUCE
664     printf("start_offset: %lu  end_offset: %lu\n", start_offset, end_offset);
665     printf("row_cnt before:  %lu\n", row_cnt);
666 #endif
667     if (start_offset) {
668         (*start) = (*start) - start_offset;
669         skiprows = reduce_factor - start_offset / cur_step;
670         srcptr += skiprows * *ds_cnt;
671         for (col = 0; col < (*ds_cnt); col++)
672             *dstptr++ = DNAN;
673         row_cnt -= skiprows;
674     }
675 #ifdef DEBUG_REDUCE
676     printf("row_cnt between: %lu\n", row_cnt);
677 #endif
678
679     /* At the end we have some rows that are not going to be
680      ** used, the amount is end_offset/cur_step
681      */
682     if (end_offset) {
683         (*end) = (*end) - end_offset + (*step);
684         skiprows = end_offset / cur_step;
685         row_cnt -= skiprows;
686     }
687 #ifdef DEBUG_REDUCE
688     printf("row_cnt after:   %lu\n", row_cnt);
689 #endif
690
691 /* Sanity check: row_cnt should be multiple of reduce_factor */
692 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
693
694     if (row_cnt % reduce_factor) {
695         printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
696                row_cnt, reduce_factor);
697         printf("BUG in reduce_data()\n");
698         exit(1);
699     }
700
701     /* Now combine reduce_factor intervals at a time
702      ** into one interval for the destination.
703      */
704
705     for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
706         for (col = 0; col < (*ds_cnt); col++) {
707             rrd_value_t newval = DNAN;
708             unsigned long validval = 0;
709
710             for (i = 0; i < reduce_factor; i++) {
711                 if (isnan(srcptr[i * (*ds_cnt) + col])) {
712                     continue;
713                 }
714                 validval++;
715                 if (isnan(newval))
716                     newval = srcptr[i * (*ds_cnt) + col];
717                 else {
718                     switch (cf) {
719                     case CF_HWPREDICT:
720                     case CF_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_file(
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
2887 /* draw that picture thing ... */
2888 int graph_paint(
2889     image_desc_t *im,
2890     char ***calcpr)
2891 {
2892     int       i, ii;
2893     int       lazy = lazy_check(im);
2894
2895     double    areazero = 0.0;
2896     graph_desc_t *lastgdes = NULL;
2897
2898     PangoFontMap *font_map = pango_cairo_font_map_get_default();
2899
2900
2901     /* if we are lazy and there is nothing to PRINT ... quit now */
2902     if (lazy && im->prt_c == 0)
2903         return 0;
2904
2905     /* pull the data from the rrd files ... */
2906
2907     if (data_fetch(im) == -1)
2908         return -1;
2909
2910     /* evaluate VDEF and CDEF operations ... */
2911     if (data_calc(im) == -1)
2912         return -1;
2913
2914
2915     /* calculate and PRINT and GPRINT definitions. We have to do it at
2916      * this point because it will affect the length of the legends
2917      * if there are no graph elements we stop here ... 
2918      * if we are lazy, try to quit ... 
2919      */
2920     i = print_calc(im, calcpr);
2921     if (i < 0)
2922         return -1;
2923     if ((i == 0) || lazy)
2924         return 0;
2925
2926 /**************************************************************
2927  *** Calculating sizes and locations became a bit confusing ***
2928  *** so I moved this into a separate function.              ***
2929  **************************************************************/
2930     if (graph_size_location(im, i) == -1)
2931         return -1;
2932
2933     /* get actual drawing data and find min and max values */
2934     if (data_proc(im) == -1)
2935         return -1;
2936
2937     if (!im->logarithmic) {
2938         si_unit(im);
2939     }
2940     /* identify si magnitude Kilo, Mega Giga ? */
2941     if (!im->rigid && !im->logarithmic)
2942         expand_range(im);   /* make sure the upper and lower limit are
2943                                sensible values */
2944
2945     if (!calc_horizontal_grid(im))
2946         return -1;
2947
2948     /* reset precalc */
2949     ytr(im, DNAN);
2950
2951 /*   if (im->gridfit)
2952      apply_gridfit(im); */
2953
2954
2955     /* the actual graph is created by going through the individual
2956        graph elements and then drawing them */
2957     cairo_surface_destroy(im->surface);
2958
2959     switch (im->imgformat) {
2960     case IF_PNG:
2961         im->surface =
2962             cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
2963                                        im->ximg * im->zoom,
2964                                        im->yimg * im->zoom);
2965         break;
2966     case IF_PDF:
2967         im->gridfit = 0;
2968         im->surface =
2969             cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
2970                                      im->yimg * im->zoom);
2971         break;
2972     case IF_EPS:
2973         im->gridfit = 0;
2974         im->surface =
2975             cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
2976                                     im->yimg * im->zoom);
2977         break;
2978     case IF_SVG:
2979         im->gridfit = 0;
2980         im->surface =
2981             cairo_svg_surface_create(im->graphfile, im->ximg * im->zoom,
2982                                      im->yimg * im->zoom);
2983         cairo_svg_surface_restrict_to_version(im->surface,
2984                                               CAIRO_SVG_VERSION_1_1);
2985         break;
2986     };
2987     im->cr = cairo_create(im->surface);
2988     pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
2989     cairo_set_antialias(im->cr, im->graph_antialias);
2990     cairo_scale(im->cr, im->zoom, im->zoom);
2991
2992     gfx_new_area(im,
2993                  0, 0,
2994                  0, im->yimg, im->ximg, im->yimg, im->graph_col[GRC_BACK]);
2995
2996     gfx_add_point(im, im->ximg, 0);
2997     gfx_close_path(im);
2998
2999     gfx_new_area(im,
3000                  im->xorigin, im->yorigin,
3001                  im->xorigin + im->xsize, im->yorigin,
3002                  im->xorigin + im->xsize, im->yorigin - im->ysize,
3003                  im->graph_col[GRC_CANVAS]);
3004
3005     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3006     gfx_close_path(im);
3007
3008     if (im->minval > 0.0)
3009         areazero = im->minval;
3010     if (im->maxval < 0.0)
3011         areazero = im->maxval;
3012
3013     for (i = 0; i < im->gdes_c; i++) {
3014         switch (im->gdes[i].gf) {
3015         case GF_CDEF:
3016         case GF_VDEF:
3017         case GF_DEF:
3018         case GF_PRINT:
3019         case GF_GPRINT:
3020         case GF_COMMENT:
3021         case GF_TEXTALIGN:
3022         case GF_HRULE:
3023         case GF_VRULE:
3024         case GF_XPORT:
3025         case GF_SHIFT:
3026             break;
3027         case GF_TICK:
3028             for (ii = 0; ii < im->xsize; ii++) {
3029                 if (!isnan(im->gdes[i].p_data[ii]) &&
3030                     im->gdes[i].p_data[ii] != 0.0) {
3031                     if (im->gdes[i].yrule > 0) {
3032                         gfx_line(im,
3033                                  im->xorigin + ii, im->yorigin,
3034                                  im->xorigin + ii,
3035                                  im->yorigin -
3036                                  im->gdes[i].yrule * im->ysize, 1.0,
3037                                  im->gdes[i].col);
3038                     } else if (im->gdes[i].yrule < 0) {
3039                         gfx_line(im,
3040                                  im->xorigin + ii,
3041                                  im->yorigin - im->ysize,
3042                                  im->xorigin + ii,
3043                                  im->yorigin - (1 -
3044                                                 im->gdes[i].yrule) *
3045                                  im->ysize, 1.0, im->gdes[i].col);
3046
3047                     }
3048                 }
3049             }
3050             break;
3051         case GF_LINE:
3052         case GF_AREA:
3053             /* fix data points at oo and -oo */
3054             for (ii = 0; ii < im->xsize; ii++) {
3055                 if (isinf(im->gdes[i].p_data[ii])) {
3056                     if (im->gdes[i].p_data[ii] > 0) {
3057                         im->gdes[i].p_data[ii] = im->maxval;
3058                     } else {
3059                         im->gdes[i].p_data[ii] = im->minval;
3060                     }
3061
3062                 }
3063             }           /* for */
3064
3065             /* *******************************************************
3066                a           ___. (a,t) 
3067                |   |    ___
3068                ____|   |   |   |
3069                |       |___|
3070                -------|--t-1--t--------------------------------      
3071
3072                if we know the value at time t was a then 
3073                we draw a square from t-1 to t with the value a.
3074
3075                ********************************************************* */
3076             if (im->gdes[i].col.alpha != 0.0) {
3077                 /* GF_LINE and friend */
3078                 if (im->gdes[i].gf == GF_LINE) {
3079                     double    last_y = 0.0;
3080                     int       draw_on = 0;
3081
3082                     cairo_save(im->cr);
3083                     cairo_new_path(im->cr);
3084
3085                     cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3086                     for (ii = 1; ii < im->xsize; ii++) {
3087                         if (isnan(im->gdes[i].p_data[ii])
3088                             || (im->slopemode == 1
3089                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3090                             draw_on = 0;
3091                             continue;
3092                         }
3093                         if (draw_on == 0) {
3094                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3095                             if (im->slopemode == 0) {
3096                                 double    x = ii - 1 + im->xorigin;
3097                                 double    y = last_y;
3098
3099                                 gfx_line_fit(im, &x, &y);
3100                                 cairo_move_to(im->cr, x, y);
3101                                 x = ii + im->xorigin;
3102                                 y = last_y;
3103                                 gfx_line_fit(im, &x, &y);
3104                                 cairo_line_to(im->cr, x, y);
3105                             } else {
3106                                 double    x = ii - 1 + im->xorigin;
3107                                 double    y = ytr(im,
3108                                                   im->gdes[i].p_data[ii - 1]);
3109
3110                                 gfx_line_fit(im, &x, &y);
3111                                 cairo_move_to(im->cr, x, y);
3112                                 x = ii + im->xorigin;
3113                                 y = last_y;
3114                                 gfx_line_fit(im, &x, &y);
3115                                 cairo_line_to(im->cr, x, y);
3116                             }
3117                             draw_on = 1;
3118                         } else {
3119                             double    x1 = ii + im->xorigin;
3120                             double    y1 = ytr(im, im->gdes[i].p_data[ii]);
3121
3122                             if (im->slopemode == 0
3123                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3124                                 double    x = ii - 1 + im->xorigin;
3125                                 double    y = y1;
3126
3127                                 gfx_line_fit(im, &x, &y);
3128                                 cairo_line_to(im->cr, x, y);
3129                             };
3130                             last_y = y1;
3131                             gfx_line_fit(im, &x1, &y1);
3132                             cairo_line_to(im->cr, x1, y1);
3133                         };
3134
3135                     }
3136                     cairo_set_source_rgba(im->cr, im->gdes[i].col.red,
3137                                           im->gdes[i].col.green,
3138                                           im->gdes[i].col.blue,
3139                                           im->gdes[i].col.alpha);
3140                     cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3141                     cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3142                     cairo_stroke(im->cr);
3143                     cairo_restore(im->cr);
3144                 } else {
3145                     int       idxI = -1;
3146                     double   *foreY = malloc(sizeof(double) * im->xsize * 2);
3147                     double   *foreX = malloc(sizeof(double) * im->xsize * 2);
3148                     double   *backY = malloc(sizeof(double) * im->xsize * 2);
3149                     double   *backX = malloc(sizeof(double) * im->xsize * 2);
3150                     int       drawem = 0;
3151
3152                     for (ii = 0; ii <= im->xsize; ii++) {
3153                         double    ybase, ytop;
3154
3155                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3156                             int       cntI = 1;
3157                             int       lastI = 0;
3158
3159                             while (cntI < idxI
3160                                    && AlmostEqual2sComplement(foreY[lastI],
3161                                                               foreY[cntI], 4)
3162                                    && AlmostEqual2sComplement(foreY[lastI],
3163                                                               foreY[cntI + 1],
3164                                                               4)) {
3165                                 cntI++;
3166                             }
3167                             gfx_new_area(im,
3168                                          backX[0], backY[0],
3169                                          foreX[0], foreY[0],
3170                                          foreX[cntI], foreY[cntI],
3171                                          im->gdes[i].col);
3172                             while (cntI < idxI) {
3173                                 lastI = cntI;
3174                                 cntI++;
3175                                 while (cntI < idxI
3176                                        &&
3177                                        AlmostEqual2sComplement(foreY[lastI],
3178                                                                foreY[cntI], 4)
3179                                        &&
3180                                        AlmostEqual2sComplement(foreY[lastI],
3181                                                                foreY[cntI +
3182                                                                      1], 4)) {
3183                                     cntI++;
3184                                 }
3185                                 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3186                             }
3187                             gfx_add_point(im, backX[idxI], backY[idxI]);
3188                             while (idxI > 1) {
3189                                 lastI = idxI;
3190                                 idxI--;
3191                                 while (idxI > 1
3192                                        &&
3193                                        AlmostEqual2sComplement(backY[lastI],
3194                                                                backY[idxI], 4)
3195                                        &&
3196                                        AlmostEqual2sComplement(backY[lastI],
3197                                                                backY[idxI -
3198                                                                      1], 4)) {
3199                                     idxI--;
3200                                 }
3201                                 gfx_add_point(im, backX[idxI], backY[idxI]);
3202                             }
3203                             idxI = -1;
3204                             drawem = 0;
3205                             gfx_close_path(im);
3206                         }
3207                         if (drawem != 0) {
3208                             drawem = 0;
3209                             idxI = -1;
3210                         }
3211                         if (ii == im->xsize)
3212                             break;
3213
3214                         if (im->slopemode == 0 && ii == 0) {
3215                             continue;
3216                         }
3217                         if (isnan(im->gdes[i].p_data[ii])) {
3218                             drawem = 1;
3219                             continue;
3220                         }
3221                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3222                         if (lastgdes && im->gdes[i].stack) {
3223                             ybase = ytr(im, lastgdes->p_data[ii]);
3224                         } else {
3225                             ybase = ytr(im, areazero);
3226                         }
3227                         if (ybase == ytop) {
3228                             drawem = 1;
3229                             continue;
3230                         }
3231
3232                         if (ybase > ytop) {
3233                             double    extra = ytop;
3234
3235                             ytop = ybase;
3236                             ybase = extra;
3237                         }
3238                         if (im->slopemode == 0) {
3239                             backY[++idxI] = ybase - 0.2;
3240                             backX[idxI] = ii + im->xorigin - 1;
3241                             foreY[idxI] = ytop + 0.2;
3242                             foreX[idxI] = ii + im->xorigin - 1;
3243                         }
3244                         backY[++idxI] = ybase - 0.2;
3245                         backX[idxI] = ii + im->xorigin;
3246                         foreY[idxI] = ytop + 0.2;
3247                         foreX[idxI] = ii + im->xorigin;
3248                     }
3249                     /* close up any remaining area */
3250                     free(foreY);
3251                     free(foreX);
3252                     free(backY);
3253                     free(backX);
3254                 }       /* else GF_LINE */
3255             }
3256             /* if color != 0x0 */
3257             /* make sure we do not run into trouble when stacking on NaN */
3258             for (ii = 0; ii < im->xsize; ii++) {
3259                 if (isnan(im->gdes[i].p_data[ii])) {
3260                     if (lastgdes && (im->gdes[i].stack)) {
3261                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3262                     } else {
3263                         im->gdes[i].p_data[ii] = areazero;
3264                     }
3265                 }
3266             }
3267             lastgdes = &(im->gdes[i]);
3268             break;
3269         case GF_STACK:
3270             rrd_set_error
3271                 ("STACK should already be turned into LINE or AREA here");
3272             return -1;
3273             break;
3274
3275         }               /* switch */
3276     }
3277
3278     /* grid_paint also does the text */
3279     if (!(im->extra_flags & ONLY_GRAPH))
3280         grid_paint(im);
3281
3282
3283     if (!(im->extra_flags & ONLY_GRAPH))
3284         axis_paint(im);
3285
3286     /* the RULES are the last thing to paint ... */
3287     for (i = 0; i < im->gdes_c; i++) {
3288
3289         switch (im->gdes[i].gf) {
3290         case GF_HRULE:
3291             if (im->gdes[i].yrule >= im->minval
3292                 && im->gdes[i].yrule <= im->maxval)
3293                 gfx_line(im,
3294                          im->xorigin, ytr(im, im->gdes[i].yrule),
3295                          im->xorigin + im->xsize, ytr(im,
3296                                                       im->gdes[i].yrule),
3297                          1.0, im->gdes[i].col);
3298             break;
3299         case GF_VRULE:
3300             if (im->gdes[i].xrule >= im->start
3301                 && im->gdes[i].xrule <= im->end)
3302                 gfx_line(im,
3303                          xtr(im, im->gdes[i].xrule), im->yorigin,
3304                          xtr(im, im->gdes[i].xrule),
3305                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3306             break;
3307         default:
3308             break;
3309         }
3310     }
3311
3312
3313     switch (im->imgformat) {
3314     case IF_PNG:
3315     {
3316         cairo_status_t status;
3317
3318         if (strcmp(im->graphfile, "-") == 0) {
3319             status =
3320                 cairo_surface_write_to_png_stream(im->surface,
3321                                                   &cairo_write_func_file,
3322                                                   (void *) stdout);
3323         } else {
3324             status = cairo_surface_write_to_png(im->surface, im->graphfile);
3325         }
3326
3327         if (status != CAIRO_STATUS_SUCCESS) {
3328             rrd_set_error("Could not save png to '%s'", im->graphfile);
3329             return 1;
3330         }
3331     }
3332         break;
3333     default:
3334         cairo_show_page(im->cr);
3335         break;
3336     }
3337     return 0;
3338 }
3339
3340
3341 /*****************************************************
3342  * graph stuff 
3343  *****************************************************/
3344
3345 int gdes_alloc(
3346     image_desc_t *im)
3347 {
3348
3349     im->gdes_c++;
3350     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3351                                                  * sizeof(graph_desc_t))) ==
3352         NULL) {
3353         rrd_set_error("realloc graph_descs");
3354         return -1;
3355     }
3356
3357
3358     im->gdes[im->gdes_c - 1].step = im->step;
3359     im->gdes[im->gdes_c - 1].step_orig = im->step;
3360     im->gdes[im->gdes_c - 1].stack = 0;
3361     im->gdes[im->gdes_c - 1].linewidth = 0;
3362     im->gdes[im->gdes_c - 1].debug = 0;
3363     im->gdes[im->gdes_c - 1].start = im->start;
3364     im->gdes[im->gdes_c - 1].start_orig = im->start;
3365     im->gdes[im->gdes_c - 1].end = im->end;
3366     im->gdes[im->gdes_c - 1].end_orig = im->end;
3367     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3368     im->gdes[im->gdes_c - 1].data = NULL;
3369     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3370     im->gdes[im->gdes_c - 1].data_first = 0;
3371     im->gdes[im->gdes_c - 1].p_data = NULL;
3372     im->gdes[im->gdes_c - 1].rpnp = NULL;
3373     im->gdes[im->gdes_c - 1].shift = 0.0;
3374     im->gdes[im->gdes_c - 1].col.red = 0.0;
3375     im->gdes[im->gdes_c - 1].col.green = 0.0;
3376     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3377     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3378     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3379     im->gdes[im->gdes_c - 1].format[0] = '\0';
3380     im->gdes[im->gdes_c - 1].strftm = 0;
3381     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3382     im->gdes[im->gdes_c - 1].ds = -1;
3383     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3384     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3385     im->gdes[im->gdes_c - 1].p_data = NULL;
3386     im->gdes[im->gdes_c - 1].yrule = DNAN;
3387     im->gdes[im->gdes_c - 1].xrule = 0;
3388     return 0;
3389 }
3390
3391 /* copies input untill the first unescaped colon is found
3392    or until input ends. backslashes have to be escaped as well */
3393 int scan_for_col(
3394     const char *const input,
3395     int len,
3396     char *const output)
3397 {
3398     int       inp, outp = 0;
3399
3400     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3401         if (input[inp] == '\\' &&
3402             input[inp + 1] != '\0' &&
3403             (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3404             output[outp++] = input[++inp];
3405         } else {
3406             output[outp++] = input[inp];
3407         }
3408     }
3409     output[outp] = '\0';
3410     return inp;
3411 }
3412
3413 /* Some surgery done on this function, it became ridiculously big.
3414 ** Things moved:
3415 ** - initializing     now in rrd_graph_init()
3416 ** - options parsing  now in rrd_graph_options()
3417 ** - script parsing   now in rrd_graph_script()
3418 */
3419 int rrd_graph(
3420     int argc,
3421     char **argv,
3422     char ***prdata,
3423     int *xsize,
3424     int *ysize,
3425     FILE * stream,
3426     double *ymin,
3427     double *ymax)
3428 {
3429     image_desc_t im;
3430
3431     rrd_graph_init(&im);
3432
3433     /* a dummy surface so that we can measure text sizes for placements */
3434     im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3435     im.cr = cairo_create(im.surface);
3436
3437
3438     /* not currently using this ... */
3439     im.graphhandle = stream;
3440
3441     rrd_graph_options(argc, argv, &im);
3442     if (rrd_test_error()) {
3443         im_free(&im);
3444         return -1;
3445     }
3446
3447     if (strlen(argv[optind]) >= MAXPATH) {
3448         rrd_set_error("filename (including path) too long");
3449         im_free(&im);
3450         return -1;
3451     }
3452     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3453     im.graphfile[MAXPATH - 1] = '\0';
3454
3455     rrd_graph_script(argc, argv, &im, 1);
3456     if (rrd_test_error()) {
3457         im_free(&im);
3458         return -1;
3459     }
3460
3461     /* Everything is now read and the actual work can start */
3462
3463     (*prdata) = NULL;
3464     if (graph_paint(&im, prdata) == -1) {
3465         im_free(&im);
3466         return -1;
3467     }
3468
3469     /* The image is generated and needs to be output.
3470      ** Also, if needed, print a line with information about the image.
3471      */
3472
3473     *xsize = im.ximg;
3474     *ysize = im.yimg;
3475     *ymin = im.minval;
3476     *ymax = im.maxval;
3477     if (im.imginfo) {
3478         char     *filename;
3479
3480         if (!(*prdata)) {
3481             /* maybe prdata is not allocated yet ... lets do it now */
3482             if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3483                 rrd_set_error("malloc imginfo");
3484                 return -1;
3485             };
3486         }
3487         if (((*prdata)[0] =
3488              malloc((strlen(im.imginfo) + 200 +
3489                      strlen(im.graphfile)) * sizeof(char)))
3490             == NULL) {
3491             rrd_set_error("malloc imginfo");
3492             return -1;
3493         }
3494         filename = im.graphfile + strlen(im.graphfile);
3495         while (filename > im.graphfile) {
3496             if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3497                 break;
3498             filename--;
3499         }
3500
3501         sprintf((*prdata)[0], im.imginfo, filename,
3502                 (long) (im.zoom * im.ximg), (long) (im.zoom * im.yimg));
3503     }
3504     im_free(&im);
3505     return 0;
3506 }
3507
3508 void rrd_graph_init(
3509     image_desc_t *im)
3510 {
3511     unsigned int i;
3512
3513 #ifdef HAVE_TZSET
3514     tzset();
3515 #endif
3516 #ifdef HAVE_SETLOCALE
3517     setlocale(LC_TIME, "");
3518 #ifdef HAVE_MBSTOWCS
3519     setlocale(LC_CTYPE, "");
3520 #endif
3521 #endif
3522     im->yorigin = 0;
3523     im->xorigin = 0;
3524     im->minval = 0;
3525     im->xlab_user.minsec = -1;
3526     im->ximg = 0;
3527     im->yimg = 0;
3528     im->xsize = 400;
3529     im->ysize = 100;
3530     im->step = 0;
3531     im->ylegend[0] = '\0';
3532     im->title[0] = '\0';
3533     im->watermark[0] = '\0';
3534     im->minval = DNAN;
3535     im->maxval = DNAN;
3536     im->unitsexponent = 9999;
3537     im->unitslength = 6;
3538     im->forceleftspace = 0;
3539     im->symbol = ' ';
3540     im->viewfactor = 1.0;
3541     im->imgformat = IF_PNG;
3542     im->cr = NULL;
3543     im->surface = NULL;
3544     im->extra_flags = 0;
3545     im->rigid = 0;
3546     im->gridfit = 1;
3547     im->imginfo = NULL;
3548     im->lazy = 0;
3549     im->slopemode = 0;
3550     im->logarithmic = 0;
3551     im->ygridstep = DNAN;
3552     im->draw_x_grid = 1;
3553     im->draw_y_grid = 1;
3554     im->base = 1000;
3555     im->prt_c = 0;
3556     im->gdes_c = 0;
3557     im->gdes = NULL;
3558     im->grid_dash_on = 1;
3559     im->grid_dash_off = 1;
3560     im->tabwidth = 40.0;
3561     im->zoom = 1;
3562     im->font_options = cairo_font_options_create();
3563     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3564
3565     cairo_font_options_set_hint_style(im->font_options,
3566                                       CAIRO_HINT_STYLE_FULL);
3567     cairo_font_options_set_hint_metrics(im->font_options,
3568                                         CAIRO_HINT_METRICS_ON);
3569     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3570
3571
3572     for (i = 0; i < DIM(graph_col); i++)
3573         im->graph_col[i] = graph_col[i];
3574
3575 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3576     {
3577         char     *windir;
3578         char      rrd_win_default_font[1000];
3579
3580         windir = getenv("windir");
3581         /* %windir% is something like D:\windows or C:\winnt */
3582         if (windir != NULL) {
3583             strncpy(rrd_win_default_font, windir, 500);
3584             rrd_win_default_font[500] = '\0';
3585             strcat(rrd_win_default_font, "\\fonts\\");
3586             strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3587             for (i = 0; i < DIM(text_prop); i++) {
3588                 strncpy(text_prop[i].font, rrd_win_default_font,
3589                         sizeof(text_prop[i].font) - 1);
3590                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3591             }
3592         }
3593     }
3594 #endif
3595     {
3596         char     *deffont;
3597
3598         deffont = getenv("RRD_DEFAULT_FONT");
3599         if (deffont != NULL) {
3600             for (i = 0; i < DIM(text_prop); i++) {
3601                 strncpy(text_prop[i].font, deffont,
3602                         sizeof(text_prop[i].font) - 1);
3603                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3604             }
3605         }
3606     }
3607     for (i = 0; i < DIM(text_prop); i++) {
3608         im->text_prop[i].size = text_prop[i].size;
3609         strcpy(im->text_prop[i].font, text_prop[i].font);
3610     }
3611 }
3612
3613 void rrd_graph_options(
3614     int argc,
3615     char *argv[],
3616     image_desc_t *im)
3617 {
3618     int       stroff;
3619     char     *parsetime_error = NULL;
3620     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3621     time_t    start_tmp = 0, end_tmp = 0;
3622     long      long_tmp;
3623     struct rrd_time_value start_tv, end_tv;
3624     long unsigned int color;
3625
3626     optind = 0;
3627     opterr = 0;         /* initialize getopt */
3628
3629     parsetime("end-24h", &start_tv);
3630     parsetime("now", &end_tv);
3631
3632     /* defines for long options without a short equivalent. should be bytes,
3633        and may not collide with (the ASCII value of) short options */
3634 #define LONGOPT_UNITS_SI 255
3635
3636     while (1) {
3637         static struct option long_options[] = {
3638             {"start", required_argument, 0, 's'},
3639             {"end", required_argument, 0, 'e'},
3640             {"x-grid", required_argument, 0, 'x'},
3641             {"y-grid", required_argument, 0, 'y'},
3642             {"vertical-label", required_argument, 0, 'v'},
3643             {"width", required_argument, 0, 'w'},
3644             {"height", required_argument, 0, 'h'},
3645             {"full-size-mode", no_argument, 0, 'D'},
3646             {"interlaced", no_argument, 0, 'i'},
3647             {"upper-limit", required_argument, 0, 'u'},
3648             {"lower-limit", required_argument, 0, 'l'},
3649             {"rigid", no_argument, 0, 'r'},
3650             {"base", required_argument, 0, 'b'},
3651             {"logarithmic", no_argument, 0, 'o'},
3652             {"color", required_argument, 0, 'c'},
3653             {"font", required_argument, 0, 'n'},
3654             {"title", required_argument, 0, 't'},
3655             {"imginfo", required_argument, 0, 'f'},
3656             {"imgformat", required_argument, 0, 'a'},
3657             {"lazy", no_argument, 0, 'z'},
3658             {"zoom", required_argument, 0, 'm'},
3659             {"no-legend", no_argument, 0, 'g'},
3660             {"force-rules-legend", no_argument, 0, 'F'},
3661             {"only-graph", no_argument, 0, 'j'},
3662             {"alt-y-grid", no_argument, 0, 'Y'},
3663             {"no-minor", no_argument, 0, 'I'},
3664             {"slope-mode", no_argument, 0, 'E'},
3665             {"alt-autoscale", no_argument, 0, 'A'},
3666             {"alt-autoscale-min", no_argument, 0, 'J'},
3667             {"alt-autoscale-max", no_argument, 0, 'M'},
3668             {"no-gridfit", no_argument, 0, 'N'},
3669             {"units-exponent", required_argument, 0, 'X'},
3670             {"units-length", required_argument, 0, 'L'},
3671             {"units", required_argument, 0, LONGOPT_UNITS_SI},
3672             {"step", required_argument, 0, 'S'},
3673             {"tabwidth", required_argument, 0, 'T'},
3674             {"font-render-mode", required_argument, 0, 'R'},
3675             {"graph-render-mode", required_argument, 0, 'G'},
3676             {"font-smoothing-threshold", required_argument, 0, 'B'},
3677             {"watermark", required_argument, 0, 'W'},
3678             {"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 */
3679             {0, 0, 0, 0}
3680         };
3681         int       option_index = 0;
3682         int       opt;
3683         int       col_start, col_end;
3684
3685         opt = getopt_long(argc, argv,
3686                           "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:",
3687                           long_options, &option_index);
3688
3689         if (opt == EOF)
3690             break;
3691
3692         switch (opt) {
3693         case 'I':
3694             im->extra_flags |= NOMINOR;
3695             break;
3696         case 'Y':
3697             im->extra_flags |= ALTYGRID;
3698             break;
3699         case 'A':
3700             im->extra_flags |= ALTAUTOSCALE;
3701             break;
3702         case 'J':
3703             im->extra_flags |= ALTAUTOSCALE_MIN;
3704             break;
3705         case 'M':
3706             im->extra_flags |= ALTAUTOSCALE_MAX;
3707             break;
3708         case 'j':
3709             im->extra_flags |= ONLY_GRAPH;
3710             break;
3711         case 'g':
3712             im->extra_flags |= NOLEGEND;
3713             break;
3714         case 'F':
3715             im->extra_flags |= FORCE_RULES_LEGEND;
3716             break;
3717         case LONGOPT_UNITS_SI:
3718             if (im->extra_flags & FORCE_UNITS) {
3719                 rrd_set_error("--units can only be used once!");
3720                 return;
3721             }
3722             if (strcmp(optarg, "si") == 0)
3723                 im->extra_flags |= FORCE_UNITS_SI;
3724             else {
3725                 rrd_set_error("invalid argument for --units: %s", optarg);
3726                 return;
3727             }
3728             break;
3729         case 'X':
3730             im->unitsexponent = atoi(optarg);
3731             break;
3732         case 'L':
3733             im->unitslength = atoi(optarg);
3734             im->forceleftspace = 1;
3735             break;
3736         case 'T':
3737             im->tabwidth = atof(optarg);
3738             break;
3739         case 'S':
3740             im->step = atoi(optarg);
3741             break;
3742         case 'N':
3743             im->gridfit = 0;
3744             break;
3745         case 's':
3746             if ((parsetime_error = parsetime(optarg, &start_tv))) {
3747                 rrd_set_error("start time: %s", parsetime_error);
3748                 return;
3749             }
3750             break;
3751         case 'e':
3752             if ((parsetime_error = parsetime(optarg, &end_tv))) {
3753                 rrd_set_error("end time: %s", parsetime_error);
3754                 return;
3755             }
3756             break;
3757         case 'x':
3758             if (strcmp(optarg, "none") == 0) {
3759                 im->draw_x_grid = 0;
3760                 break;
3761             };
3762
3763             if (sscanf(optarg,
3764                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3765                        scan_gtm,
3766                        &im->xlab_user.gridst,
3767                        scan_mtm,
3768                        &im->xlab_user.mgridst,
3769                        scan_ltm,
3770                        &im->xlab_user.labst,
3771                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3772                 strncpy(im->xlab_form, optarg + stroff,
3773                         sizeof(im->xlab_form) - 1);
3774                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3775                 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3776                     rrd_set_error("unknown keyword %s", scan_gtm);
3777                     return;
3778                 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3779                            == -1) {
3780                     rrd_set_error("unknown keyword %s", scan_mtm);
3781                     return;
3782                 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3783                            -1) {
3784                     rrd_set_error("unknown keyword %s", scan_ltm);
3785                     return;
3786                 }
3787                 im->xlab_user.minsec = 1;
3788                 im->xlab_user.stst = im->xlab_form;
3789             } else {
3790                 rrd_set_error("invalid x-grid format");
3791                 return;
3792             }
3793             break;
3794         case 'y':
3795
3796             if (strcmp(optarg, "none") == 0) {
3797                 im->draw_y_grid = 0;
3798                 break;
3799             };
3800
3801             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3802                 if (im->ygridstep <= 0) {
3803                     rrd_set_error("grid step must be > 0");
3804                     return;
3805                 } else if (im->ylabfact < 1) {
3806                     rrd_set_error("label factor must be > 0");
3807                     return;
3808                 }
3809             } else {
3810                 rrd_set_error("invalid y-grid format");
3811                 return;
3812             }
3813             break;
3814         case 'v':
3815             strncpy(im->ylegend, optarg, 150);
3816             im->ylegend[150] = '\0';
3817             break;
3818         case 'u':
3819             im->maxval = atof(optarg);
3820             break;
3821         case 'l':
3822             im->minval = atof(optarg);
3823             break;
3824         case 'b':
3825             im->base = atol(optarg);
3826             if (im->base != 1024 && im->base != 1000) {
3827                 rrd_set_error
3828                     ("the only sensible value for base apart from 1000 is 1024");
3829                 return;
3830             }
3831             break;
3832         case 'w':
3833             long_tmp = atol(optarg);
3834             if (long_tmp < 10) {
3835                 rrd_set_error("width below 10 pixels");
3836                 return;
3837             }
3838             im->xsize = long_tmp;
3839             break;
3840         case 'h':
3841             long_tmp = atol(optarg);
3842             if (long_tmp < 10) {
3843                 rrd_set_error("height below 10 pixels");
3844                 return;
3845             }
3846             im->ysize = long_tmp;
3847             break;
3848         case 'D':
3849             im->extra_flags |= FULL_SIZE_MODE;
3850             break;
3851         case 'i':
3852             /* interlaced png not supported at the moment */
3853             break;
3854         case 'r':
3855             im->rigid = 1;
3856             break;
3857         case 'f':
3858             im->imginfo = optarg;
3859             break;
3860         case 'a':
3861             if ((int) (im->imgformat = if_conv(optarg)) == -1) {
3862                 rrd_set_error("unsupported graphics format '%s'", optarg);
3863                 return;
3864             }
3865             break;
3866         case 'z':
3867             im->lazy = 1;
3868             break;
3869         case 'E':
3870             im->slopemode = 1;
3871             break;
3872
3873         case 'o':
3874             im->logarithmic = 1;
3875             break;
3876         case 'c':
3877             if (sscanf(optarg,
3878                        "%10[A-Z]#%n%8lx%n",
3879                        col_nam, &col_start, &color, &col_end) == 2) {
3880                 int       ci;
3881                 int       col_len = col_end - col_start;
3882
3883                 switch (col_len) {
3884                 case 3:
3885                     color = (((color & 0xF00) * 0x110000) |
3886                              ((color & 0x0F0) * 0x011000) |
3887                              ((color & 0x00F) * 0x001100) | 0x000000FF);
3888                     break;
3889                 case 4:
3890                     color = (((color & 0xF000) * 0x11000) |
3891                              ((color & 0x0F00) * 0x01100) |
3892                              ((color & 0x00F0) * 0x00110) |
3893                              ((color & 0x000F) * 0x00011)
3894                         );
3895                     break;
3896                 case 6:
3897                     color = (color << 8) + 0xff /* shift left by 8 */ ;
3898                     break;
3899                 case 8:
3900                     break;
3901                 default:
3902                     rrd_set_error("the color format is #RRGGBB[AA]");
3903                     return;
3904                 }
3905                 if ((ci = grc_conv(col_nam)) != -1) {
3906                     im->graph_col[ci] = gfx_hex_to_col(color);
3907                 } else {
3908                     rrd_set_error("invalid color name '%s'", col_nam);
3909                     return;
3910                 }
3911             } else {
3912                 rrd_set_error("invalid color def format");
3913                 return;
3914             }
3915             break;
3916         case 'n':{
3917             char      prop[15];
3918             double    size = 1;
3919             char      font[1024] = "";
3920
3921             if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
3922                 int       sindex, propidx;
3923
3924                 if ((sindex = text_prop_conv(prop)) != -1) {
3925                     for (propidx = sindex; propidx < TEXT_PROP_LAST;
3926                          propidx++) {
3927                         if (size > 0) {
3928                             im->text_prop[propidx].size = size;
3929                         }
3930                         if (strlen(font) > 0) {
3931                             strcpy(im->text_prop[propidx].font, font);
3932                         }
3933                         if (propidx == sindex && sindex != 0)
3934                             break;
3935                     }
3936                 } else {
3937                     rrd_set_error("invalid fonttag '%s'", prop);
3938                     return;
3939                 }
3940             } else {
3941                 rrd_set_error("invalid text property format");
3942                 return;
3943             }
3944             break;
3945         }
3946         case 'm':
3947             im->zoom = atof(optarg);
3948             if (im->zoom <= 0.0) {
3949                 rrd_set_error("zoom factor must be > 0");
3950                 return;
3951             }
3952             break;
3953         case 't':
3954             strncpy(im->title, optarg, 150);
3955             im->title[150] = '\0';
3956             break;
3957
3958         case 'R':
3959             if (strcmp(optarg, "normal") == 0) {
3960                 cairo_font_options_set_antialias(im->font_options,
3961                                                  CAIRO_ANTIALIAS_GRAY);
3962                 cairo_font_options_set_hint_style(im->font_options,
3963                                                   CAIRO_HINT_STYLE_FULL);
3964             } else if (strcmp(optarg, "light") == 0) {
3965                 cairo_font_options_set_antialias(im->font_options,
3966                                                  CAIRO_ANTIALIAS_GRAY);
3967                 cairo_font_options_set_hint_style(im->font_options,
3968                                                   CAIRO_HINT_STYLE_SLIGHT);
3969             } else if (strcmp(optarg, "mono") == 0) {
3970                 cairo_font_options_set_antialias(im->font_options,
3971                                                  CAIRO_ANTIALIAS_NONE);
3972                 cairo_font_options_set_hint_style(im->font_options,
3973                                                   CAIRO_HINT_STYLE_FULL);
3974             } else {
3975                 rrd_set_error("unknown font-render-mode '%s'", optarg);
3976                 return;
3977             }
3978             break;
3979         case 'G':
3980             if (strcmp(optarg, "normal") == 0)
3981                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3982             else if (strcmp(optarg, "mono") == 0)
3983                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
3984             else {
3985                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
3986                 return;
3987             }
3988             break;
3989         case 'B':
3990             /* not supported curently */
3991             break;
3992
3993         case 'W':
3994             strncpy(im->watermark, optarg, 100);
3995             im->watermark[99] = '\0';
3996             break;
3997
3998         case '?':
3999             if (optopt != 0)
4000                 rrd_set_error("unknown option '%c'", optopt);
4001             else
4002                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4003             return;
4004         }
4005     }
4006
4007     if (optind >= argc) {
4008         rrd_set_error("missing filename");
4009         return;
4010     }
4011
4012     if (im->logarithmic == 1 && im->minval <= 0) {
4013         rrd_set_error
4014             ("for a logarithmic yaxis you must specify a lower-limit > 0");
4015         return;
4016     }
4017
4018     if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4019         /* error string is set in parsetime.c */
4020         return;
4021     }
4022
4023     if (start_tmp < 3600 * 24 * 365 * 10) {
4024         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
4025                       start_tmp);
4026         return;
4027     }
4028
4029     if (end_tmp < start_tmp) {
4030         rrd_set_error("start (%ld) should be less than end (%ld)",
4031                       start_tmp, end_tmp);
4032         return;
4033     }
4034
4035     im->start = start_tmp;
4036     im->end = end_tmp;
4037     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4038 }
4039
4040 int rrd_graph_color(
4041     image_desc_t *im,
4042     char *var,
4043     char *err,
4044     int optional)
4045 {
4046     char     *color;
4047     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4048
4049     color = strstr(var, "#");
4050     if (color == NULL) {
4051         if (optional == 0) {
4052             rrd_set_error("Found no color in %s", err);
4053             return 0;
4054         }
4055         return 0;
4056     } else {
4057         int       n = 0;
4058         char     *rest;
4059         long unsigned int col;
4060
4061         rest = strstr(color, ":");
4062         if (rest != NULL)
4063             n = rest - color;
4064         else
4065             n = strlen(color);
4066
4067         switch (n) {
4068         case 7:
4069             sscanf(color, "#%6lx%n", &col, &n);
4070             col = (col << 8) + 0xff /* shift left by 8 */ ;
4071             if (n != 7)
4072                 rrd_set_error("Color problem in %s", err);
4073             break;
4074         case 9:
4075             sscanf(color, "#%8lx%n", &col, &n);
4076             if (n == 9)
4077                 break;
4078         default:
4079             rrd_set_error("Color problem in %s", err);
4080         }
4081         if (rrd_test_error())
4082             return 0;
4083         gdp->col = gfx_hex_to_col(col);
4084         return n;
4085     }
4086 }
4087
4088
4089 int bad_format(
4090     char *fmt)
4091 {
4092     char     *ptr;
4093     int       n = 0;
4094
4095     ptr = fmt;
4096     while (*ptr != '\0')
4097         if (*ptr++ == '%') {
4098
4099             /* line cannot end with percent char */
4100             if (*ptr == '\0')
4101                 return 1;
4102
4103             /* '%s', '%S' and '%%' are allowed */
4104             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4105                 ptr++;
4106
4107             /* %c is allowed (but use only with vdef!) */
4108             else if (*ptr == 'c') {
4109                 ptr++;
4110                 n = 1;
4111             }
4112
4113             /* or else '% 6.2lf' and such are allowed */
4114             else {
4115                 /* optional padding character */
4116                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4117                     ptr++;
4118
4119                 /* This should take care of 'm.n' with all three optional */
4120                 while (*ptr >= '0' && *ptr <= '9')
4121                     ptr++;
4122                 if (*ptr == '.')
4123                     ptr++;
4124                 while (*ptr >= '0' && *ptr <= '9')
4125                     ptr++;
4126
4127                 /* Either 'le', 'lf' or 'lg' must follow here */
4128                 if (*ptr++ != 'l')
4129                     return 1;
4130                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4131                     ptr++;
4132                 else
4133                     return 1;
4134                 n++;
4135             }
4136         }
4137
4138     return (n != 1);
4139 }
4140
4141
4142 int vdef_parse(
4143     gdes,
4144     str)
4145     struct graph_desc_t *gdes;
4146     const char *const str;
4147 {
4148     /* A VDEF currently is either "func" or "param,func"
4149      * so the parsing is rather simple.  Change if needed.
4150      */
4151     double    param;
4152     char      func[30];
4153     int       n;
4154
4155     n = 0;
4156     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4157     if (n == (int) strlen(str)) {   /* matched */
4158         ;
4159     } else {
4160         n = 0;
4161         sscanf(str, "%29[A-Z]%n", func, &n);
4162         if (n == (int) strlen(str)) {   /* matched */
4163             param = DNAN;
4164         } else {
4165             rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4166                           gdes->vname);
4167             return -1;
4168         }
4169     }
4170     if (!strcmp("PERCENT", func))
4171         gdes->vf.op = VDEF_PERCENT;
4172     else if (!strcmp("MAXIMUM", func))
4173         gdes->vf.op = VDEF_MAXIMUM;
4174     else if (!strcmp("AVERAGE", func))
4175         gdes->vf.op = VDEF_AVERAGE;
4176     else if (!strcmp("MINIMUM", func))
4177         gdes->vf.op = VDEF_MINIMUM;
4178     else if (!strcmp("TOTAL", func))
4179         gdes->vf.op = VDEF_TOTAL;
4180     else if (!strcmp("FIRST", func))
4181         gdes->vf.op = VDEF_FIRST;
4182     else if (!strcmp("LAST", func))
4183         gdes->vf.op = VDEF_LAST;
4184     else if (!strcmp("LSLSLOPE", func))
4185         gdes->vf.op = VDEF_LSLSLOPE;
4186     else if (!strcmp("LSLINT", func))
4187         gdes->vf.op = VDEF_LSLINT;
4188     else if (!strcmp("LSLCORREL", func))
4189         gdes->vf.op = VDEF_LSLCORREL;
4190     else {
4191         rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4192                       gdes->vname);
4193         return -1;
4194     };
4195
4196     switch (gdes->vf.op) {
4197     case VDEF_PERCENT:
4198         if (isnan(param)) { /* no parameter given */
4199             rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4200                           func, gdes->vname);
4201             return -1;
4202         };
4203         if (param >= 0.0 && param <= 100.0) {
4204             gdes->vf.param = param;
4205             gdes->vf.val = DNAN;    /* undefined */
4206             gdes->vf.when = 0;  /* undefined */
4207         } else {
4208             rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4209                           gdes->vname);
4210             return -1;
4211         };
4212         break;
4213     case VDEF_MAXIMUM:
4214     case VDEF_AVERAGE:
4215     case VDEF_MINIMUM:
4216     case VDEF_TOTAL:
4217     case VDEF_FIRST:
4218     case VDEF_LAST:
4219     case VDEF_LSLSLOPE:
4220     case VDEF_LSLINT:
4221     case VDEF_LSLCORREL:
4222         if (isnan(param)) {
4223             gdes->vf.param = DNAN;
4224             gdes->vf.val = DNAN;
4225             gdes->vf.when = 0;
4226         } else {
4227             rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4228                           func, gdes->vname);
4229             return -1;
4230         };
4231         break;
4232     };
4233     return 0;
4234 }
4235
4236
4237 int vdef_calc(
4238     im,
4239     gdi)
4240     image_desc_t *im;
4241     int gdi;
4242 {
4243     graph_desc_t *src, *dst;
4244     rrd_value_t *data;
4245     long      step, steps;
4246
4247     dst = &im->gdes[gdi];
4248     src = &im->gdes[dst->vidx];
4249     data = src->data + src->ds;
4250     steps = (src->end - src->start) / src->step;
4251
4252 #if 0
4253     printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4254            src->end, steps);
4255 #endif
4256
4257     switch (dst->vf.op) {
4258     case VDEF_PERCENT:{
4259         rrd_value_t *array;
4260         int       field;
4261
4262
4263         if ((array = malloc(steps * sizeof(double))) == NULL) {
4264             rrd_set_error("malloc VDEV_PERCENT");
4265             return -1;
4266         }
4267         for (step = 0; step < steps; step++) {
4268             array[step] = data[step * src->ds_cnt];
4269         }
4270         qsort(array, step, sizeof(double), vdef_percent_compar);
4271
4272         field = (steps - 1) * dst->vf.param / 100;
4273         dst->vf.val = array[field];
4274         dst->vf.when = 0;   /* no time component */
4275         free(array);
4276 #if 0
4277         for (step = 0; step < steps; step++)
4278             printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4279                    step == field ? '*' : ' ');
4280 #endif
4281     }
4282         break;
4283     case VDEF_MAXIMUM:
4284         step = 0;
4285         while (step != steps && isnan(data[step * src->ds_cnt]))
4286             step++;
4287         if (step == steps) {
4288             dst->vf.val = DNAN;
4289             dst->vf.when = 0;
4290         } else {
4291             dst->vf.val = data[step * src->ds_cnt];
4292             dst->vf.when = src->start + (step + 1) * src->step;
4293         }
4294         while (step != steps) {
4295             if (finite(data[step * src->ds_cnt])) {
4296                 if (data[step * src->ds_cnt] > dst->vf.val) {
4297                     dst->vf.val = data[step * src->ds_cnt];
4298                     dst->vf.when = src->start + (step + 1) * src->step;
4299                 }
4300             }
4301             step++;
4302         }
4303         break;
4304     case VDEF_TOTAL:
4305     case VDEF_AVERAGE:{
4306         int       cnt = 0;
4307         double    sum = 0.0;
4308
4309         for (step = 0; step < steps; step++) {
4310             if (finite(data[step * src->ds_cnt])) {
4311                 sum += data[step * src->ds_cnt];
4312                 cnt++;
4313             };
4314         }
4315         if (cnt) {
4316             if (dst->vf.op == VDEF_TOTAL) {
4317                 dst->vf.val = sum * src->step;
4318                 dst->vf.when = 0;   /* no time component */
4319             } else {
4320                 dst->vf.val = sum / cnt;
4321                 dst->vf.when = 0;   /* no time component */
4322             };
4323         } else {
4324             dst->vf.val = DNAN;
4325             dst->vf.when = 0;
4326         }
4327     }
4328         break;
4329     case VDEF_MINIMUM:
4330         step = 0;
4331         while (step != steps && isnan(data[step * src->ds_cnt]))
4332             step++;
4333         if (step == steps) {
4334             dst->vf.val = DNAN;
4335             dst->vf.when = 0;
4336         } else {
4337             dst->vf.val = data[step * src->ds_cnt];
4338             dst->vf.when = src->start + (step + 1) * src->step;
4339         }
4340         while (step != steps) {
4341             if (finite(data[step * src->ds_cnt])) {
4342                 if (data[step * src->ds_cnt] < dst->vf.val) {
4343                     dst->vf.val = data[step * src->ds_cnt];
4344                     dst->vf.when = src->start + (step + 1) * src->step;
4345                 }
4346             }
4347             step++;
4348         }
4349         break;
4350     case VDEF_FIRST:
4351         /* The time value returned here is one step before the
4352          * actual time value.  This is the start of the first
4353          * non-NaN interval.
4354          */
4355         step = 0;
4356         while (step != steps && isnan(data[step * src->ds_cnt]))
4357             step++;
4358         if (step == steps) {    /* all entries were NaN */
4359             dst->vf.val = DNAN;
4360             dst->vf.when = 0;
4361         } else {
4362             dst->vf.val = data[step * src->ds_cnt];
4363             dst->vf.when = src->start + step * src->step;
4364         }
4365         break;
4366     case VDEF_LAST:
4367         /* The time value returned here is the
4368          * actual time value.  This is the end of the last
4369          * non-NaN interval.
4370          */
4371         step = steps - 1;
4372         while (step >= 0 && isnan(data[step * src->ds_cnt]))
4373             step--;
4374         if (step < 0) { /* all entries were NaN */
4375             dst->vf.val = DNAN;
4376             dst->vf.when = 0;
4377         } else {
4378             dst->vf.val = data[step * src->ds_cnt];
4379             dst->vf.when = src->start + (step + 1) * src->step;
4380         }
4381         break;
4382     case VDEF_LSLSLOPE:
4383     case VDEF_LSLINT:
4384     case VDEF_LSLCORREL:{
4385         /* Bestfit line by linear least squares method */
4386
4387         int       cnt = 0;
4388         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4389
4390         SUMx = 0;
4391         SUMy = 0;
4392         SUMxy = 0;
4393         SUMxx = 0;
4394         SUMyy = 0;
4395
4396         for (step = 0; step < steps; step++) {
4397             if (finite(data[step * src->ds_cnt])) {
4398                 cnt++;
4399                 SUMx += step;
4400                 SUMxx += step * step;
4401                 SUMxy += step * data[step * src->ds_cnt];
4402                 SUMy += data[step * src->ds_cnt];
4403                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4404             };
4405         }
4406
4407         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4408         y_intercept = (SUMy - slope * SUMx) / cnt;
4409         correl =
4410             (SUMxy -
4411              (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4412                                           (SUMx * SUMx) / cnt) * (SUMyy -
4413                                                                   (SUMy *
4414                                                                    SUMy) /
4415                                                                   cnt));
4416
4417         if (cnt) {
4418             if (dst->vf.op == VDEF_LSLSLOPE) {
4419                 dst->vf.val = slope;
4420                 dst->vf.when = 0;
4421             } else if (dst->vf.op == VDEF_LSLINT) {
4422                 dst->vf.val = y_intercept;
4423                 dst->vf.when = 0;
4424             } else if (dst->vf.op == VDEF_LSLCORREL) {
4425                 dst->vf.val = correl;
4426                 dst->vf.when = 0;
4427             };
4428
4429         } else {
4430             dst->vf.val = DNAN;
4431             dst->vf.when = 0;
4432         }
4433     }
4434         break;
4435     }
4436     return 0;
4437 }
4438
4439 /* NaN < -INF < finite_values < INF */
4440 int vdef_percent_compar(
4441     a,
4442     b)
4443     const void *a, *b;
4444 {
4445     /* Equality is not returned; this doesn't hurt except
4446      * (maybe) for a little performance.
4447      */
4448
4449     /* First catch NaN values. They are smallest */
4450     if (isnan(*(double *) a))
4451         return -1;
4452     if (isnan(*(double *) b))
4453         return 1;
4454
4455     /* NaN doesn't reach this part so INF and -INF are extremes.
4456      * The sign from isinf() is compatible with the sign we return
4457      */
4458     if (isinf(*(double *) a))
4459         return isinf(*(double *) a);
4460     if (isinf(*(double *) b))
4461         return isinf(*(double *) b);
4462
4463     /* If we reach this, both values must be finite */
4464     if (*(double *) a < *(double *) b)
4465         return -1;
4466     else
4467         return 1;
4468 }