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