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