disable gridfitting for vector formats
[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     gfx_new_area(im, im->xorigin + im->xsize + 2, im->yorigin - 2, im->xorigin + im->xsize + 2, im->yorigin + 3, im->xorigin + im->xsize + 7, im->yorigin + 0.5,    /* LINEOFFSET */
2410                  im->graph_col[GRC_ARROW]);
2411     gfx_close_path(im);
2412
2413     gfx_new_area(im, im->xorigin - 2, im->yorigin - im->ysize - 2, im->xorigin + 3, im->yorigin - im->ysize - 2, im->xorigin + 0.5, im->yorigin - im->ysize - 7,    /* LINEOFFSET */
2414                  im->graph_col[GRC_ARROW]);
2415     gfx_close_path(im);
2416
2417
2418 }
2419
2420 void grid_paint(
2421     image_desc_t *im)
2422 {
2423     long      i;
2424     int       res = 0;
2425     double    X0, Y0;   /* points for filled graph and more */
2426     struct gfx_color_t water_color;
2427
2428     /* draw 3d border */
2429     gfx_new_area(im, 0, im->yimg,
2430                  2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2431     gfx_add_point(im, im->ximg - 2, 2);
2432     gfx_add_point(im, im->ximg, 0);
2433     gfx_add_point(im, 0, 0);
2434     gfx_close_path(im);
2435
2436     gfx_new_area(im, 2, im->yimg - 2,
2437                  im->ximg - 2, im->yimg - 2,
2438                  im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2439     gfx_add_point(im, im->ximg, 0);
2440     gfx_add_point(im, im->ximg, im->yimg);
2441     gfx_add_point(im, 0, im->yimg);
2442     gfx_close_path(im);
2443
2444
2445     if (im->draw_x_grid == 1)
2446         vertical_grid(im);
2447
2448     if (im->draw_y_grid == 1) {
2449         if (im->logarithmic) {
2450             res = horizontal_log_grid(im);
2451         } else {
2452             res = draw_horizontal_grid(im);
2453         }
2454
2455         /* dont draw horizontal grid if there is no min and max val */
2456         if (!res) {
2457             char     *nodata = "No Data found";
2458
2459             gfx_text(im, im->ximg / 2,
2460                      (2 * im->yorigin - im->ysize) / 2,
2461                      im->graph_col[GRC_FONT],
2462                      im->text_prop[TEXT_PROP_AXIS].font,
2463                      im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2464                      0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2465         }
2466     }
2467
2468     /* yaxis unit description */
2469     gfx_text(im,
2470              10, (im->yorigin - im->ysize / 2),
2471              im->graph_col[GRC_FONT],
2472              im->text_prop[TEXT_PROP_UNIT].font,
2473              im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2474              RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2475
2476     /* graph title */
2477     gfx_text(im,
2478              im->ximg / 2, 6,
2479              im->graph_col[GRC_FONT],
2480              im->text_prop[TEXT_PROP_TITLE].font,
2481              im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2482              GFX_H_CENTER, GFX_V_TOP, im->title);
2483     /* rrdtool 'logo' */
2484     water_color = im->graph_col[GRC_FONT];
2485     water_color.alpha = 0.3;
2486     gfx_text(im,
2487              im->ximg - 4, 5,
2488              water_color,
2489              im->text_prop[TEXT_PROP_AXIS].font,
2490              5.5, im->tabwidth, -90,
2491              GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2492
2493     /* graph watermark */
2494     if (im->watermark[0] != '\0') {
2495         gfx_text(im,
2496                  im->ximg / 2, im->yimg - 6,
2497                  water_color,
2498                  im->text_prop[TEXT_PROP_AXIS].font,
2499                  5.5, im->tabwidth, 0,
2500                  GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2501     }
2502
2503     /* graph labels */
2504     if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2505         for (i = 0; i < im->gdes_c; i++) {
2506             if (im->gdes[i].legend[0] == '\0')
2507                 continue;
2508
2509             /* im->gdes[i].leg_y is the bottom of the legend */
2510             X0 = im->gdes[i].leg_x;
2511             Y0 = im->gdes[i].leg_y;
2512             gfx_text(im, X0, Y0,
2513                      im->graph_col[GRC_FONT],
2514                      im->text_prop[TEXT_PROP_LEGEND].font,
2515                      im->text_prop[TEXT_PROP_LEGEND].size,
2516                      im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2517                      im->gdes[i].legend);
2518             /* The legend for GRAPH items starts with "M " to have
2519                enough space for the box */
2520             if (im->gdes[i].gf != GF_PRINT &&
2521                 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2522                 double    boxH, boxV;
2523                 double    X1, Y1;
2524
2525
2526                 boxH = gfx_get_text_width(im, 0,
2527                                           im->text_prop[TEXT_PROP_LEGEND].
2528                                           font,
2529                                           im->text_prop[TEXT_PROP_LEGEND].
2530                                           size, im->tabwidth, "o") * 1.2;
2531                 boxV = boxH;
2532
2533                 /* shift the box up a bit */
2534                 Y0 -= boxV * 0.4;
2535
2536                 /* make sure transparent colors show up the same way as in the graph */
2537
2538                 gfx_new_area(im,
2539                              X0, Y0 - boxV,
2540                              X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2541                 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2542                 gfx_close_path(im);
2543
2544                 gfx_new_area(im,
2545                              X0, Y0 - boxV,
2546                              X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2547                 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2548                 gfx_close_path(im);
2549
2550                 cairo_save(im->cr);
2551                 cairo_new_path(im->cr);
2552                 cairo_set_line_width(im->cr, 1.0);
2553                 X1 = X0 + boxH;
2554                 Y1 = Y0 - boxV;
2555                 gfx_line_fit(im, &X0, &Y0);
2556                 gfx_line_fit(im, &X1, &Y1);
2557                 cairo_move_to(im->cr, X0, Y0);
2558                 cairo_line_to(im->cr, X1, Y0);
2559                 cairo_line_to(im->cr, X1, Y1);
2560                 cairo_line_to(im->cr, X0, Y1);
2561                 cairo_close_path(im->cr);
2562                 cairo_set_source_rgba(im->cr, im->graph_col[GRC_FRAME].red,
2563                                       im->graph_col[GRC_FRAME].green,
2564                                       im->graph_col[GRC_FRAME].blue,
2565                                       im->graph_col[GRC_FRAME].alpha);
2566                 cairo_stroke(im->cr);
2567                 cairo_restore(im->cr);
2568             }
2569         }
2570     }
2571 }
2572
2573
2574 /*****************************************************
2575  * lazy check make sure we rely need to create this graph
2576  *****************************************************/
2577
2578 int lazy_check(
2579     image_desc_t *im)
2580 {
2581     FILE     *fd = NULL;
2582     int       size = 1;
2583     struct stat imgstat;
2584
2585     if (im->lazy == 0)
2586         return 0;       /* no lazy option */
2587     if (stat(im->graphfile, &imgstat) != 0)
2588         return 0;       /* can't stat */
2589     /* one pixel in the existing graph is more then what we would
2590        change here ... */
2591     if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2592         return 0;
2593     if ((fd = fopen(im->graphfile, "rb")) == NULL)
2594         return 0;       /* the file does not exist */
2595     switch (im->imgformat) {
2596     case IF_PNG:
2597         size = PngSize(fd, &(im->ximg), &(im->yimg));
2598         break;
2599     default:
2600         size = 1;
2601     }
2602     fclose(fd);
2603     return size;
2604 }
2605
2606
2607 int graph_size_location(
2608     image_desc_t *im,
2609     int elements)
2610 {
2611     /* The actual size of the image to draw is determined from
2612      ** several sources.  The size given on the command line is
2613      ** the graph area but we need more as we have to draw labels
2614      ** and other things outside the graph area
2615      */
2616
2617     int       Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2618         Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2619
2620     if (im->extra_flags & ONLY_GRAPH) {
2621         im->xorigin = 0;
2622         im->ximg = im->xsize;
2623         im->yimg = im->ysize;
2624         im->yorigin = im->ysize;
2625         ytr(im, DNAN);
2626         return 0;
2627     }
2628
2629     /** +---+--------------------------------------------+
2630      ** | y |...............graph title..................|
2631      ** |   +---+-------------------------------+--------+
2632      ** | a | y |                               |        |
2633      ** | x |   |                               |        |
2634      ** | i | a |                               |  pie   |
2635      ** | s | x |       main graph area         | chart  |
2636      ** |   | i |                               |  area  |
2637      ** | t | s |                               |        |
2638      ** | i |   |                               |        |
2639      ** | t | l |                               |        |
2640      ** | l | b +-------------------------------+--------+
2641      ** | e | l |       x axis labels           |        |
2642      ** +---+---+-------------------------------+--------+
2643      ** |....................legends.....................|
2644      ** +------------------------------------------------+
2645      ** |                   watermark                    |
2646      ** +------------------------------------------------+
2647      */
2648
2649     if (im->ylegend[0] != '\0') {
2650         Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2651     }
2652
2653     if (im->title[0] != '\0') {
2654         /* The title is placed "inbetween" two text lines so it
2655          ** automatically has some vertical spacing.  The horizontal
2656          ** spacing is added here, on each side.
2657          */
2658         /* if necessary, reduce the font size of the title until it fits the image width */
2659         Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2660     }
2661
2662     if (elements) {
2663         if (im->draw_x_grid) {
2664             Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2665         }
2666         if (im->draw_y_grid || im->forceleftspace) {
2667             Xylabel = gfx_get_text_width(im, 0,
2668                                          im->text_prop[TEXT_PROP_AXIS].font,
2669                                          im->text_prop[TEXT_PROP_AXIS].size,
2670                                          im->tabwidth, "0") * im->unitslength;
2671         }
2672     }
2673
2674     if (im->extra_flags & FULL_SIZE_MODE) {
2675         /* The actual size of the image to draw has been determined by the user.
2676          ** The graph area is the space remaining after accounting for the legend,
2677          ** the watermark, the pie chart, the axis labels, and the title.
2678          */
2679         im->xorigin = 0;
2680         im->ximg = im->xsize;
2681         im->yimg = im->ysize;
2682         im->yorigin = im->ysize;
2683         Xmain = im->ximg;
2684         Ymain = im->yimg;
2685
2686         im->yorigin += Ytitle;
2687
2688         /* Now calculate the total size.  Insert some spacing where
2689            desired.  im->xorigin and im->yorigin need to correspond
2690            with the lower left corner of the main graph area or, if
2691            this one is not set, the imaginary box surrounding the
2692            pie chart area. */
2693
2694         /* Initial size calculation for the main graph area */
2695         Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2696         if (Xmain)
2697             Xmain -= Xspacing;  /* put space between main graph area and right edge */
2698
2699         im->xorigin = Xspacing + Xylabel;
2700
2701         /* the length of the title should not influence with width of the graph
2702            if (Xtitle > im->ximg) im->ximg = Xtitle; */
2703
2704         if (Xvertical) {    /* unit description */
2705             Xmain -= Xvertical;
2706             im->xorigin += Xvertical;
2707         }
2708         im->xsize = Xmain;
2709         xtr(im, 0);
2710
2711         /* The vertical size of the image is known in advance.  The main graph area
2712          ** (Ymain) and im->yorigin must be set according to the space requirements
2713          ** of the legend and the axis labels.
2714          */
2715
2716         if (im->extra_flags & NOLEGEND) {
2717             /* set dimensions correctly if using full size mode with no legend */
2718             im->yorigin =
2719                 im->yimg - im->text_prop[TEXT_PROP_AXIS].size * 2.5 -
2720                 Yspacing;
2721             Ymain = im->yorigin;
2722         } else {
2723             /* Determine where to place the legends onto the image.
2724              ** Set Ymain and adjust im->yorigin to match the space requirements.
2725              */
2726             if (leg_place(im, &Ymain) == -1)
2727                 return -1;
2728         }
2729
2730
2731         /* remove title space *or* some padding above the graph from the main graph area */
2732         if (Ytitle) {
2733             Ymain -= Ytitle;
2734         } else {
2735             Ymain -= 1.5 * Yspacing;
2736         }
2737
2738         /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2739         if (im->watermark[0] != '\0') {
2740             Ymain -= Ywatermark;
2741         }
2742
2743         im->ysize = Ymain;
2744
2745     } else {            /* dimension options -width and -height refer to the dimensions of the main graph area */
2746
2747         /* The actual size of the image to draw is determined from
2748          ** several sources.  The size given on the command line is
2749          ** the graph area but we need more as we have to draw labels
2750          ** and other things outside the graph area.
2751          */
2752
2753         if (im->ylegend[0] != '\0') {
2754             Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2755         }
2756
2757
2758         if (im->title[0] != '\0') {
2759             /* The title is placed "inbetween" two text lines so it
2760              ** automatically has some vertical spacing.  The horizontal
2761              ** spacing is added here, on each side.
2762              */
2763             /* don't care for the with of the title
2764                Xtitle = gfx_get_text_width(im->canvas, 0,
2765                im->text_prop[TEXT_PROP_TITLE].font,
2766                im->text_prop[TEXT_PROP_TITLE].size,
2767                im->tabwidth,
2768                im->title, 0) + 2*Xspacing; */
2769             Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2770         }
2771
2772         if (elements) {
2773             Xmain = im->xsize;
2774             Ymain = im->ysize;
2775         }
2776         /* Now calculate the total size.  Insert some spacing where
2777            desired.  im->xorigin and im->yorigin need to correspond
2778            with the lower left corner of the main graph area or, if
2779            this one is not set, the imaginary box surrounding the
2780            pie chart area. */
2781
2782         /* The legend width cannot yet be determined, as a result we
2783          ** have problems adjusting the image to it.  For now, we just
2784          ** forget about it at all; the legend will have to fit in the
2785          ** size already allocated.
2786          */
2787         im->ximg = Xylabel + Xmain + 2 * Xspacing;
2788
2789         if (Xmain)
2790             im->ximg += Xspacing;
2791
2792         im->xorigin = Xspacing + Xylabel;
2793
2794         /* the length of the title should not influence with width of the graph
2795            if (Xtitle > im->ximg) im->ximg = Xtitle; */
2796
2797         if (Xvertical) {    /* unit description */
2798             im->ximg += Xvertical;
2799             im->xorigin += Xvertical;
2800         }
2801         xtr(im, 0);
2802
2803         /* The vertical size is interesting... we need to compare
2804          ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with 
2805          ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2806          ** in order to start even thinking about Ylegend or Ywatermark.
2807          **
2808          ** Do it in three portions: First calculate the inner part,
2809          ** then do the legend, then adjust the total height of the img,
2810          ** adding space for a watermark if one exists;
2811          */
2812
2813         /* reserve space for main and/or pie */
2814
2815         im->yimg = Ymain + Yxlabel;
2816
2817
2818         im->yorigin = im->yimg - Yxlabel;
2819
2820         /* reserve space for the title *or* some padding above the graph */
2821         if (Ytitle) {
2822             im->yimg += Ytitle;
2823             im->yorigin += Ytitle;
2824         } else {
2825             im->yimg += 1.5 * Yspacing;
2826             im->yorigin += 1.5 * Yspacing;
2827         }
2828         /* reserve space for padding below the graph */
2829         im->yimg += Yspacing;
2830
2831         /* Determine where to place the legends onto the image.
2832          ** Adjust im->yimg to match the space requirements.
2833          */
2834         if (leg_place(im, 0) == -1)
2835             return -1;
2836
2837         if (im->watermark[0] != '\0') {
2838             im->yimg += Ywatermark;
2839         }
2840     }
2841
2842     ytr(im, DNAN);
2843     return 0;
2844 }
2845
2846 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2847 /* yes we are loosing precision by doing tos with floats instead of doubles
2848    but it seems more stable this way. */
2849
2850
2851 /* draw that picture thing ... */
2852 int graph_paint(
2853     image_desc_t *im,
2854     char ***calcpr)
2855 {
2856     int       i, ii;
2857     int       lazy = lazy_check(im);
2858
2859     double    areazero = 0.0;
2860     graph_desc_t *lastgdes = NULL;
2861
2862     PangoFontMap *font_map = pango_cairo_font_map_get_default();
2863
2864
2865     /* if we are lazy and there is nothing to PRINT ... quit now */
2866     if (lazy && im->prt_c == 0)
2867         return 0;
2868
2869     /* pull the data from the rrd files ... */
2870
2871     if (data_fetch(im) == -1)
2872         return -1;
2873
2874     /* evaluate VDEF and CDEF operations ... */
2875     if (data_calc(im) == -1)
2876         return -1;
2877
2878
2879     /* calculate and PRINT and GPRINT definitions. We have to do it at
2880      * this point because it will affect the length of the legends
2881      * if there are no graph elements we stop here ... 
2882      * if we are lazy, try to quit ... 
2883      */
2884     i = print_calc(im, calcpr);
2885     if (i < 0)
2886         return -1;
2887     if ((i == 0) || lazy)
2888         return 0;
2889
2890 /**************************************************************
2891  *** Calculating sizes and locations became a bit confusing ***
2892  *** so I moved this into a separate function.              ***
2893  **************************************************************/
2894     if (graph_size_location(im, i) == -1)
2895         return -1;
2896
2897     /* get actual drawing data and find min and max values */
2898     if (data_proc(im) == -1)
2899         return -1;
2900
2901     if (!im->logarithmic) {
2902         si_unit(im);
2903     }
2904     /* identify si magnitude Kilo, Mega Giga ? */
2905     if (!im->rigid && !im->logarithmic)
2906         expand_range(im);   /* make sure the upper and lower limit are
2907                                sensible values */
2908
2909     if (!calc_horizontal_grid(im))
2910         return -1;
2911
2912     /* reset precalc */
2913     ytr(im, DNAN);
2914
2915 /*   if (im->gridfit)
2916      apply_gridfit(im); */
2917
2918
2919     /* the actual graph is created by going through the individual
2920        graph elements and then drawing them */
2921     cairo_surface_destroy(im->surface);
2922
2923     switch (im->imgformat) {
2924     case IF_PNG:
2925         im->surface =
2926             cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
2927                                        im->ximg * im->zoom,
2928                                        im->yimg * im->zoom);
2929         break;
2930     case IF_PDF:
2931         im->gridfit = 0;
2932         im->surface =
2933             cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
2934                                      im->yimg * im->zoom);
2935         break;
2936     case IF_EPS:
2937         im->gridfit = 0;
2938         im->surface =
2939             cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
2940                                     im->yimg * im->zoom);
2941         break;
2942     case IF_SVG:
2943         im->gridfit = 0;
2944         im->surface =
2945             cairo_svg_surface_create(im->graphfile, im->ximg * im->zoom,
2946                                      im->yimg * im->zoom);
2947         cairo_svg_surface_restrict_to_version(im->surface,
2948                                               CAIRO_SVG_VERSION_1_1);
2949         break;
2950     };
2951     im->cr = cairo_create(im->surface);
2952     pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
2953     cairo_set_antialias(im->cr, im->graph_antialias);
2954     cairo_scale(im->cr, im->zoom, im->zoom);
2955
2956     gfx_new_area(im,
2957                  0, 0,
2958                  0, im->yimg, im->ximg, im->yimg, im->graph_col[GRC_BACK]);
2959
2960     gfx_add_point(im, im->ximg, 0);
2961     gfx_close_path(im);
2962
2963     gfx_new_area(im,
2964                  im->xorigin, im->yorigin,
2965                  im->xorigin + im->xsize, im->yorigin,
2966                  im->xorigin + im->xsize, im->yorigin - im->ysize,
2967                  im->graph_col[GRC_CANVAS]);
2968
2969     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
2970     gfx_close_path(im);
2971
2972     if (im->minval > 0.0)
2973         areazero = im->minval;
2974     if (im->maxval < 0.0)
2975         areazero = im->maxval;
2976
2977     for (i = 0; i < im->gdes_c; i++) {
2978         switch (im->gdes[i].gf) {
2979         case GF_CDEF:
2980         case GF_VDEF:
2981         case GF_DEF:
2982         case GF_PRINT:
2983         case GF_GPRINT:
2984         case GF_COMMENT:
2985         case GF_HRULE:
2986         case GF_VRULE:
2987         case GF_XPORT:
2988         case GF_SHIFT:
2989             break;
2990         case GF_TICK:
2991             for (ii = 0; ii < im->xsize; ii++) {
2992                 if (!isnan(im->gdes[i].p_data[ii]) &&
2993                     im->gdes[i].p_data[ii] != 0.0) {
2994                     if (im->gdes[i].yrule > 0) {
2995                         gfx_line(im,
2996                                  im->xorigin + ii, im->yorigin,
2997                                  im->xorigin + ii,
2998                                  im->yorigin -
2999                                  im->gdes[i].yrule * im->ysize, 1.0,
3000                                  im->gdes[i].col);
3001                     } else if (im->gdes[i].yrule < 0) {
3002                         gfx_line(im,
3003                                  im->xorigin + ii,
3004                                  im->yorigin - im->ysize,
3005                                  im->xorigin + ii,
3006                                  im->yorigin - (1 -
3007                                                 im->gdes[i].yrule) *
3008                                  im->ysize, 1.0, im->gdes[i].col);
3009
3010                     }
3011                 }
3012             }
3013             break;
3014         case GF_LINE:
3015         case GF_AREA:
3016             /* fix data points at oo and -oo */
3017             for (ii = 0; ii < im->xsize; ii++) {
3018                 if (isinf(im->gdes[i].p_data[ii])) {
3019                     if (im->gdes[i].p_data[ii] > 0) {
3020                         im->gdes[i].p_data[ii] = im->maxval;
3021                     } else {
3022                         im->gdes[i].p_data[ii] = im->minval;
3023                     }
3024
3025                 }
3026             }           /* for */
3027
3028             /* *******************************************************
3029                a           ___. (a,t) 
3030                |   |    ___
3031                ____|   |   |   |
3032                |       |___|
3033                -------|--t-1--t--------------------------------      
3034
3035                if we know the value at time t was a then 
3036                we draw a square from t-1 to t with the value a.
3037
3038                ********************************************************* */
3039             if (im->gdes[i].col.alpha != 0.0) {
3040                 /* GF_LINE and friend */
3041                 if (im->gdes[i].gf == GF_LINE) {
3042                     double    last_y = 0.0;
3043                     int       draw_on = 0;
3044
3045                     cairo_save(im->cr);
3046                     cairo_new_path(im->cr);
3047
3048                     cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3049                     for (ii = 1; ii < im->xsize; ii++) {
3050                         if (isnan(im->gdes[i].p_data[ii])
3051                             || (im->slopemode == 1
3052                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3053                             draw_on = 0;
3054                             continue;
3055                         }
3056                         if (draw_on == 0) {
3057                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3058                             if (im->slopemode == 0) {
3059                                 double    x = ii - 1 + im->xorigin;
3060                                 double    y = last_y;
3061
3062                                 gfx_line_fit(im, &x, &y);
3063                                 cairo_move_to(im->cr, x, y);
3064                                 x = ii + im->xorigin;
3065                                 y = last_y;
3066                                 gfx_line_fit(im, &x, &y);
3067                                 cairo_line_to(im->cr, x, y);
3068                             } else {
3069                                 double    x = ii - 1 + im->xorigin;
3070                                 double    y = ytr(im,
3071                                                   im->gdes[i].p_data[ii - 1]);
3072
3073                                 gfx_line_fit(im, &x, &y);
3074                                 cairo_move_to(im->cr, x, y);
3075                                 x = ii + im->xorigin;
3076                                 y = last_y;
3077                                 gfx_line_fit(im, &x, &y);
3078                                 cairo_line_to(im->cr, x, y);
3079                             }
3080                             draw_on = 1;
3081                         } else {
3082                             double    x1 = ii + im->xorigin;
3083                             double    y1 = ytr(im, im->gdes[i].p_data[ii]);
3084
3085                             if (im->slopemode == 0
3086                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3087                                 double    x = ii - 1 + im->xorigin;
3088                                 double    y = y1;
3089
3090                                 gfx_line_fit(im, &x, &y);
3091                                 cairo_line_to(im->cr, x, y);
3092                             };
3093                             last_y = y1;
3094                             gfx_line_fit(im, &x1, &y1);
3095                             cairo_line_to(im->cr, x1, y1);
3096                         };
3097
3098                     }
3099                     cairo_set_source_rgba(im->cr, im->gdes[i].col.red,
3100                                           im->gdes[i].col.green,
3101                                           im->gdes[i].col.blue,
3102                                           im->gdes[i].col.alpha);
3103                     cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3104                     cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3105                     cairo_stroke(im->cr);
3106                     cairo_restore(im->cr);
3107                 } else {
3108                     int       idxI = -1;
3109                     double   *foreY = malloc(sizeof(double) * im->xsize * 2);
3110                     double   *foreX = malloc(sizeof(double) * im->xsize * 2);
3111                     double   *backY = malloc(sizeof(double) * im->xsize * 2);
3112                     double   *backX = malloc(sizeof(double) * im->xsize * 2);
3113                     int       drawem = 0;
3114
3115                     for (ii = 0; ii <= im->xsize; ii++) {
3116                         double    ybase, ytop;
3117
3118                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3119                             int       cntI = 1;
3120                             int       lastI = 0;
3121
3122                             while (cntI < idxI
3123                                    && AlmostEqual2sComplement(foreY[lastI],
3124                                                               foreY[cntI], 4)
3125                                    && AlmostEqual2sComplement(foreY[lastI],
3126                                                               foreY[cntI + 1],
3127                                                               4)) {
3128                                 cntI++;
3129                             }
3130                             gfx_new_area(im,
3131                                          backX[0], backY[0],
3132                                          foreX[0], foreY[0],
3133                                          foreX[cntI], foreY[cntI],
3134                                          im->gdes[i].col);
3135                             while (cntI < idxI) {
3136                                 lastI = cntI;
3137                                 cntI++;
3138                                 while (cntI < idxI
3139                                        &&
3140                                        AlmostEqual2sComplement(foreY[lastI],
3141                                                                foreY[cntI], 4)
3142                                        &&
3143                                        AlmostEqual2sComplement(foreY[lastI],
3144                                                                foreY[cntI +
3145                                                                      1], 4)) {
3146                                     cntI++;
3147                                 }
3148                                 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3149                             }
3150                             gfx_add_point(im, backX[idxI], backY[idxI]);
3151                             while (idxI > 1) {
3152                                 lastI = idxI;
3153                                 idxI--;
3154                                 while (idxI > 1
3155                                        &&
3156                                        AlmostEqual2sComplement(backY[lastI],
3157                                                                backY[idxI], 4)
3158                                        &&
3159                                        AlmostEqual2sComplement(backY[lastI],
3160                                                                backY[idxI -
3161                                                                      1], 4)) {
3162                                     idxI--;
3163                                 }
3164                                 gfx_add_point(im, backX[idxI], backY[idxI]);
3165                             }
3166                             idxI = -1;
3167                             drawem = 0;
3168                             gfx_close_path(im);
3169                         }
3170                         if (drawem != 0) {
3171                             drawem = 0;
3172                             idxI = -1;
3173                         }
3174                         if (ii == im->xsize)
3175                             break;
3176
3177                         if (im->slopemode == 0 && ii == 0) {
3178                             continue;
3179                         }
3180                         if (isnan(im->gdes[i].p_data[ii])) {
3181                             drawem = 1;
3182                             continue;
3183                         }
3184                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3185                         if (lastgdes && im->gdes[i].stack) {
3186                             ybase = ytr(im, lastgdes->p_data[ii]);
3187                         } else {
3188                             ybase = ytr(im, areazero);
3189                         }
3190                         if (ybase == ytop) {
3191                             drawem = 1;
3192                             continue;
3193                         }
3194
3195                         if (ybase > ytop) {
3196                             double    extra = ytop;
3197
3198                             ytop = ybase;
3199                             ybase = extra;
3200                         }
3201                         if (im->slopemode == 0) {
3202                             backY[++idxI] = ybase - 0.2;
3203                             backX[idxI] = ii + im->xorigin - 1;
3204                             foreY[idxI] = ytop + 0.2;
3205                             foreX[idxI] = ii + im->xorigin - 1;
3206                         }
3207                         backY[++idxI] = ybase - 0.2;
3208                         backX[idxI] = ii + im->xorigin;
3209                         foreY[idxI] = ytop + 0.2;
3210                         foreX[idxI] = ii + im->xorigin;
3211                     }
3212                     /* close up any remaining area */
3213                     free(foreY);
3214                     free(foreX);
3215                     free(backY);
3216                     free(backX);
3217                 }       /* else GF_LINE */
3218             }
3219             /* if color != 0x0 */
3220             /* make sure we do not run into trouble when stacking on NaN */
3221             for (ii = 0; ii < im->xsize; ii++) {
3222                 if (isnan(im->gdes[i].p_data[ii])) {
3223                     if (lastgdes && (im->gdes[i].stack)) {
3224                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3225                     } else {
3226                         im->gdes[i].p_data[ii] = areazero;
3227                     }
3228                 }
3229             }
3230             lastgdes = &(im->gdes[i]);
3231             break;
3232         case GF_STACK:
3233             rrd_set_error
3234                 ("STACK should already be turned into LINE or AREA here");
3235             return -1;
3236             break;
3237
3238         }               /* switch */
3239     }
3240
3241     /* grid_paint also does the text */
3242     if (!(im->extra_flags & ONLY_GRAPH))
3243         grid_paint(im);
3244
3245
3246     if (!(im->extra_flags & ONLY_GRAPH))
3247         axis_paint(im);
3248
3249     /* the RULES are the last thing to paint ... */
3250     for (i = 0; i < im->gdes_c; i++) {
3251
3252         switch (im->gdes[i].gf) {
3253         case GF_HRULE:
3254             if (im->gdes[i].yrule >= im->minval
3255                 && im->gdes[i].yrule <= im->maxval)
3256                 gfx_line(im,
3257                          im->xorigin, ytr(im, im->gdes[i].yrule),
3258                          im->xorigin + im->xsize, ytr(im,
3259                                                       im->gdes[i].yrule),
3260                          1.0, im->gdes[i].col);
3261             break;
3262         case GF_VRULE:
3263             if (im->gdes[i].xrule >= im->start
3264                 && im->gdes[i].xrule <= im->end)
3265                 gfx_line(im,
3266                          xtr(im, im->gdes[i].xrule), im->yorigin,
3267                          xtr(im, im->gdes[i].xrule),
3268                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3269             break;
3270         default:
3271             break;
3272         }
3273     }
3274
3275
3276     switch (im->imgformat) {
3277     case IF_PNG:
3278         if (cairo_surface_write_to_png(im->surface, im->graphfile) !=
3279             CAIRO_STATUS_SUCCESS) {
3280             rrd_set_error("Could not save png to '%s'", im->graphfile);
3281             return 1;
3282         }
3283         break;
3284     default:
3285         cairo_show_page(im->cr);
3286         break;
3287     }
3288     return 0;
3289 }
3290
3291
3292 /*****************************************************
3293  * graph stuff 
3294  *****************************************************/
3295
3296 int gdes_alloc(
3297     image_desc_t *im)
3298 {
3299
3300     im->gdes_c++;
3301     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3302                                                  * sizeof(graph_desc_t))) ==
3303         NULL) {
3304         rrd_set_error("realloc graph_descs");
3305         return -1;
3306     }
3307
3308
3309     im->gdes[im->gdes_c - 1].step = im->step;
3310     im->gdes[im->gdes_c - 1].step_orig = im->step;
3311     im->gdes[im->gdes_c - 1].stack = 0;
3312     im->gdes[im->gdes_c - 1].linewidth = 0;
3313     im->gdes[im->gdes_c - 1].debug = 0;
3314     im->gdes[im->gdes_c - 1].start = im->start;
3315     im->gdes[im->gdes_c - 1].start_orig = im->start;
3316     im->gdes[im->gdes_c - 1].end = im->end;
3317     im->gdes[im->gdes_c - 1].end_orig = im->end;
3318     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3319     im->gdes[im->gdes_c - 1].data = NULL;
3320     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3321     im->gdes[im->gdes_c - 1].data_first = 0;
3322     im->gdes[im->gdes_c - 1].p_data = NULL;
3323     im->gdes[im->gdes_c - 1].rpnp = NULL;
3324     im->gdes[im->gdes_c - 1].shift = 0.0;
3325     im->gdes[im->gdes_c - 1].col.red = 0.0;
3326     im->gdes[im->gdes_c - 1].col.green = 0.0;
3327     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3328     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3329     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3330     im->gdes[im->gdes_c - 1].format[0] = '\0';
3331     im->gdes[im->gdes_c - 1].strftm = 0;
3332     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3333     im->gdes[im->gdes_c - 1].ds = -1;
3334     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3335     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3336     im->gdes[im->gdes_c - 1].p_data = NULL;
3337     im->gdes[im->gdes_c - 1].yrule = DNAN;
3338     im->gdes[im->gdes_c - 1].xrule = 0;
3339     return 0;
3340 }
3341
3342 /* copies input untill the first unescaped colon is found
3343    or until input ends. backslashes have to be escaped as well */
3344 int scan_for_col(
3345     const char *const input,
3346     int len,
3347     char *const output)
3348 {
3349     int       inp, outp = 0;
3350
3351     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3352         if (input[inp] == '\\' &&
3353             input[inp + 1] != '\0' &&
3354             (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3355             output[outp++] = input[++inp];
3356         } else {
3357             output[outp++] = input[inp];
3358         }
3359     }
3360     output[outp] = '\0';
3361     return inp;
3362 }
3363
3364 /* Some surgery done on this function, it became ridiculously big.
3365 ** Things moved:
3366 ** - initializing     now in rrd_graph_init()
3367 ** - options parsing  now in rrd_graph_options()
3368 ** - script parsing   now in rrd_graph_script()
3369 */
3370 int rrd_graph(
3371     int argc,
3372     char **argv,
3373     char ***prdata,
3374     int *xsize,
3375     int *ysize,
3376     FILE * stream,
3377     double *ymin,
3378     double *ymax)
3379 {
3380     image_desc_t im;
3381
3382     rrd_graph_init(&im);
3383
3384     /* a dummy surface so that we can measure text sizes for placements */
3385     im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3386     im.cr = cairo_create(im.surface);
3387
3388
3389     /* not currently using this ... */
3390     im.graphhandle = stream;
3391
3392     rrd_graph_options(argc, argv, &im);
3393     if (rrd_test_error()) {
3394         im_free(&im);
3395         return -1;
3396     }
3397
3398     if (strlen(argv[optind]) >= MAXPATH) {
3399         rrd_set_error("filename (including path) too long");
3400         im_free(&im);
3401         return -1;
3402     }
3403     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3404     im.graphfile[MAXPATH - 1] = '\0';
3405
3406     rrd_graph_script(argc, argv, &im, 1);
3407     if (rrd_test_error()) {
3408         im_free(&im);
3409         return -1;
3410     }
3411
3412     /* Everything is now read and the actual work can start */
3413
3414     (*prdata) = NULL;
3415     if (graph_paint(&im, prdata) == -1) {
3416         im_free(&im);
3417         return -1;
3418     }
3419
3420     /* The image is generated and needs to be output.
3421      ** Also, if needed, print a line with information about the image.
3422      */
3423
3424     *xsize = im.ximg;
3425     *ysize = im.yimg;
3426     *ymin = im.minval;
3427     *ymax = im.maxval;
3428     if (im.imginfo) {
3429         char     *filename;
3430
3431         if (!(*prdata)) {
3432             /* maybe prdata is not allocated yet ... lets do it now */
3433             if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3434                 rrd_set_error("malloc imginfo");
3435                 return -1;
3436             };
3437         }
3438         if (((*prdata)[0] =
3439              malloc((strlen(im.imginfo) + 200 +
3440                      strlen(im.graphfile)) * sizeof(char)))
3441             == NULL) {
3442             rrd_set_error("malloc imginfo");
3443             return -1;
3444         }
3445         filename = im.graphfile + strlen(im.graphfile);
3446         while (filename > im.graphfile) {
3447             if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3448                 break;
3449             filename--;
3450         }
3451
3452         sprintf((*prdata)[0], im.imginfo, filename,
3453                 (long) (im.zoom * im.ximg), (long) (im.zoom * im.yimg));
3454     }
3455     im_free(&im);
3456     return 0;
3457 }
3458
3459 void rrd_graph_init(
3460     image_desc_t *im)
3461 {
3462     unsigned int i;
3463
3464 #ifdef HAVE_TZSET
3465     tzset();
3466 #endif
3467 #ifdef HAVE_SETLOCALE
3468     setlocale(LC_TIME, "");
3469 #ifdef HAVE_MBSTOWCS
3470     setlocale(LC_CTYPE, "");
3471 #endif
3472 #endif
3473     im->yorigin = 0;
3474     im->xorigin = 0;
3475     im->minval = 0;
3476     im->xlab_user.minsec = -1;
3477     im->ximg = 0;
3478     im->yimg = 0;
3479     im->xsize = 400;
3480     im->ysize = 100;
3481     im->step = 0;
3482     im->ylegend[0] = '\0';
3483     im->title[0] = '\0';
3484     im->watermark[0] = '\0';
3485     im->minval = DNAN;
3486     im->maxval = DNAN;
3487     im->unitsexponent = 9999;
3488     im->unitslength = 6;
3489     im->forceleftspace = 0;
3490     im->symbol = ' ';
3491     im->viewfactor = 1.0;
3492     im->imgformat = IF_PNG;
3493     im->cr = NULL;
3494     im->surface = NULL;
3495     im->extra_flags = 0;
3496     im->rigid = 0;
3497     im->gridfit = 1;
3498     im->imginfo = NULL;
3499     im->lazy = 0;
3500     im->slopemode = 0;
3501     im->logarithmic = 0;
3502     im->ygridstep = DNAN;
3503     im->draw_x_grid = 1;
3504     im->draw_y_grid = 1;
3505     im->base = 1000;
3506     im->prt_c = 0;
3507     im->gdes_c = 0;
3508     im->gdes = NULL;
3509     im->grid_dash_on = 1;
3510     im->grid_dash_off = 1;
3511     im->tabwidth = 40.0;
3512     im->zoom = 1;
3513     im->font_options = cairo_font_options_create();
3514     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3515
3516     cairo_font_options_set_hint_style(im->font_options,
3517                                       CAIRO_HINT_STYLE_FULL);
3518     cairo_font_options_set_hint_metrics(im->font_options,
3519                                         CAIRO_HINT_METRICS_ON);
3520     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3521
3522
3523     for (i = 0; i < DIM(graph_col); i++)
3524         im->graph_col[i] = graph_col[i];
3525
3526 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3527     {
3528         char     *windir;
3529         char      rrd_win_default_font[1000];
3530
3531         windir = getenv("windir");
3532         /* %windir% is something like D:\windows or C:\winnt */
3533         if (windir != NULL) {
3534             strncpy(rrd_win_default_font, windir, 500);
3535             rrd_win_default_font[500] = '\0';
3536             strcat(rrd_win_default_font, "\\fonts\\");
3537             strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3538             for (i = 0; i < DIM(text_prop); i++) {
3539                 strncpy(text_prop[i].font, rrd_win_default_font,
3540                         sizeof(text_prop[i].font) - 1);
3541                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3542             }
3543         }
3544     }
3545 #endif
3546     {
3547         char     *deffont;
3548
3549         deffont = getenv("RRD_DEFAULT_FONT");
3550         if (deffont != NULL) {
3551             for (i = 0; i < DIM(text_prop); i++) {
3552                 strncpy(text_prop[i].font, deffont,
3553                         sizeof(text_prop[i].font) - 1);
3554                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3555             }
3556         }
3557     }
3558     for (i = 0; i < DIM(text_prop); i++) {
3559         im->text_prop[i].size = text_prop[i].size;
3560         strcpy(im->text_prop[i].font, text_prop[i].font);
3561     }
3562 }
3563
3564 void rrd_graph_options(
3565     int argc,
3566     char *argv[],
3567     image_desc_t *im)
3568 {
3569     int       stroff;
3570     char     *parsetime_error = NULL;
3571     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3572     time_t    start_tmp = 0, end_tmp = 0;
3573     long      long_tmp;
3574     struct rrd_time_value start_tv, end_tv;
3575     long unsigned int color;
3576
3577     optind = 0;
3578     opterr = 0;         /* initialize getopt */
3579
3580     parsetime("end-24h", &start_tv);
3581     parsetime("now", &end_tv);
3582
3583     /* defines for long options without a short equivalent. should be bytes,
3584        and may not collide with (the ASCII value of) short options */
3585 #define LONGOPT_UNITS_SI 255
3586
3587     while (1) {
3588         static struct option long_options[] = {
3589             {"start", required_argument, 0, 's'},
3590             {"end", required_argument, 0, 'e'},
3591             {"x-grid", required_argument, 0, 'x'},
3592             {"y-grid", required_argument, 0, 'y'},
3593             {"vertical-label", required_argument, 0, 'v'},
3594             {"width", required_argument, 0, 'w'},
3595             {"height", required_argument, 0, 'h'},
3596             {"full-size-mode", no_argument, 0, 'D'},
3597             {"interlaced", no_argument, 0, 'i'},
3598             {"upper-limit", required_argument, 0, 'u'},
3599             {"lower-limit", required_argument, 0, 'l'},
3600             {"rigid", no_argument, 0, 'r'},
3601             {"base", required_argument, 0, 'b'},
3602             {"logarithmic", no_argument, 0, 'o'},
3603             {"color", required_argument, 0, 'c'},
3604             {"font", required_argument, 0, 'n'},
3605             {"title", required_argument, 0, 't'},
3606             {"imginfo", required_argument, 0, 'f'},
3607             {"imgformat", required_argument, 0, 'a'},
3608             {"lazy", no_argument, 0, 'z'},
3609             {"zoom", required_argument, 0, 'm'},
3610             {"no-legend", no_argument, 0, 'g'},
3611             {"force-rules-legend", no_argument, 0, 'F'},
3612             {"only-graph", no_argument, 0, 'j'},
3613             {"alt-y-grid", no_argument, 0, 'Y'},
3614             {"no-minor", no_argument, 0, 'I'},
3615             {"slope-mode", no_argument, 0, 'E'},
3616             {"alt-autoscale", no_argument, 0, 'A'},
3617             {"alt-autoscale-min", no_argument, 0, 'J'},
3618             {"alt-autoscale-max", no_argument, 0, 'M'},
3619             {"no-gridfit", no_argument, 0, 'N'},
3620             {"units-exponent", required_argument, 0, 'X'},
3621             {"units-length", required_argument, 0, 'L'},
3622             {"units", required_argument, 0, LONGOPT_UNITS_SI},
3623             {"step", required_argument, 0, 'S'},
3624             {"tabwidth", required_argument, 0, 'T'},
3625             {"font-render-mode", required_argument, 0, 'R'},
3626             {"graph-render-mode", required_argument, 0, 'G'},
3627             {"font-smoothing-threshold", required_argument, 0, 'B'},
3628             {"watermark", required_argument, 0, 'W'},
3629             {"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 */
3630             {0, 0, 0, 0}
3631         };
3632         int       option_index = 0;
3633         int       opt;
3634         int       col_start, col_end;
3635
3636         opt = getopt_long(argc, argv,
3637                           "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:",
3638                           long_options, &option_index);
3639
3640         if (opt == EOF)
3641             break;
3642
3643         switch (opt) {
3644         case 'I':
3645             im->extra_flags |= NOMINOR;
3646             break;
3647         case 'Y':
3648             im->extra_flags |= ALTYGRID;
3649             break;
3650         case 'A':
3651             im->extra_flags |= ALTAUTOSCALE;
3652             break;
3653         case 'J':
3654             im->extra_flags |= ALTAUTOSCALE_MIN;
3655             break;
3656         case 'M':
3657             im->extra_flags |= ALTAUTOSCALE_MAX;
3658             break;
3659         case 'j':
3660             im->extra_flags |= ONLY_GRAPH;
3661             break;
3662         case 'g':
3663             im->extra_flags |= NOLEGEND;
3664             break;
3665         case 'F':
3666             im->extra_flags |= FORCE_RULES_LEGEND;
3667             break;
3668         case LONGOPT_UNITS_SI:
3669             if (im->extra_flags & FORCE_UNITS) {
3670                 rrd_set_error("--units can only be used once!");
3671                 return;
3672             }
3673             if (strcmp(optarg, "si") == 0)
3674                 im->extra_flags |= FORCE_UNITS_SI;
3675             else {
3676                 rrd_set_error("invalid argument for --units: %s", optarg);
3677                 return;
3678             }
3679             break;
3680         case 'X':
3681             im->unitsexponent = atoi(optarg);
3682             break;
3683         case 'L':
3684             im->unitslength = atoi(optarg);
3685             im->forceleftspace = 1;
3686             break;
3687         case 'T':
3688             im->tabwidth = atof(optarg);
3689             break;
3690         case 'S':
3691             im->step = atoi(optarg);
3692             break;
3693         case 'N':
3694             im->gridfit = 0;
3695             break;
3696         case 's':
3697             if ((parsetime_error = parsetime(optarg, &start_tv))) {
3698                 rrd_set_error("start time: %s", parsetime_error);
3699                 return;
3700             }
3701             break;
3702         case 'e':
3703             if ((parsetime_error = parsetime(optarg, &end_tv))) {
3704                 rrd_set_error("end time: %s", parsetime_error);
3705                 return;
3706             }
3707             break;
3708         case 'x':
3709             if (strcmp(optarg, "none") == 0) {
3710                 im->draw_x_grid = 0;
3711                 break;
3712             };
3713
3714             if (sscanf(optarg,
3715                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3716                        scan_gtm,
3717                        &im->xlab_user.gridst,
3718                        scan_mtm,
3719                        &im->xlab_user.mgridst,
3720                        scan_ltm,
3721                        &im->xlab_user.labst,
3722                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3723                 strncpy(im->xlab_form, optarg + stroff,
3724                         sizeof(im->xlab_form) - 1);
3725                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3726                 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3727                     rrd_set_error("unknown keyword %s", scan_gtm);
3728                     return;
3729                 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3730                            == -1) {
3731                     rrd_set_error("unknown keyword %s", scan_mtm);
3732                     return;
3733                 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3734                            -1) {
3735                     rrd_set_error("unknown keyword %s", scan_ltm);
3736                     return;
3737                 }
3738                 im->xlab_user.minsec = 1;
3739                 im->xlab_user.stst = im->xlab_form;
3740             } else {
3741                 rrd_set_error("invalid x-grid format");
3742                 return;
3743             }
3744             break;
3745         case 'y':
3746
3747             if (strcmp(optarg, "none") == 0) {
3748                 im->draw_y_grid = 0;
3749                 break;
3750             };
3751
3752             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3753                 if (im->ygridstep <= 0) {
3754                     rrd_set_error("grid step must be > 0");
3755                     return;
3756                 } else if (im->ylabfact < 1) {
3757                     rrd_set_error("label factor must be > 0");
3758                     return;
3759                 }
3760             } else {
3761                 rrd_set_error("invalid y-grid format");
3762                 return;
3763             }
3764             break;
3765         case 'v':
3766             strncpy(im->ylegend, optarg, 150);
3767             im->ylegend[150] = '\0';
3768             break;
3769         case 'u':
3770             im->maxval = atof(optarg);
3771             break;
3772         case 'l':
3773             im->minval = atof(optarg);
3774             break;
3775         case 'b':
3776             im->base = atol(optarg);
3777             if (im->base != 1024 && im->base != 1000) {
3778                 rrd_set_error
3779                     ("the only sensible value for base apart from 1000 is 1024");
3780                 return;
3781             }
3782             break;
3783         case 'w':
3784             long_tmp = atol(optarg);
3785             if (long_tmp < 10) {
3786                 rrd_set_error("width below 10 pixels");
3787                 return;
3788             }
3789             im->xsize = long_tmp;
3790             break;
3791         case 'h':
3792             long_tmp = atol(optarg);
3793             if (long_tmp < 10) {
3794                 rrd_set_error("height below 10 pixels");
3795                 return;
3796             }
3797             im->ysize = long_tmp;
3798             break;
3799         case 'D':
3800             im->extra_flags |= FULL_SIZE_MODE;
3801             break;
3802         case 'i':
3803             /* interlaced png not supported at the moment */
3804             break;
3805         case 'r':
3806             im->rigid = 1;
3807             break;
3808         case 'f':
3809             im->imginfo = optarg;
3810             break;
3811         case 'a':
3812             if ((int) (im->imgformat = if_conv(optarg)) == -1) {
3813                 rrd_set_error("unsupported graphics format '%s'", optarg);
3814                 return;
3815             }
3816             break;
3817         case 'z':
3818             im->lazy = 1;
3819             break;
3820         case 'E':
3821             im->slopemode = 1;
3822             break;
3823
3824         case 'o':
3825             im->logarithmic = 1;
3826             break;
3827         case 'c':
3828             if (sscanf(optarg,
3829                        "%10[A-Z]#%n%8lx%n",
3830                        col_nam, &col_start, &color, &col_end) == 2) {
3831                 int       ci;
3832                 int       col_len = col_end - col_start;
3833
3834                 switch (col_len) {
3835                 case 3:
3836                     color = (((color & 0xF00) * 0x110000) |
3837                              ((color & 0x0F0) * 0x011000) |
3838                              ((color & 0x00F) * 0x001100) | 0x000000FF);
3839                     break;
3840                 case 4:
3841                     color = (((color & 0xF000) * 0x11000) |
3842                              ((color & 0x0F00) * 0x01100) |
3843                              ((color & 0x00F0) * 0x00110) |
3844                              ((color & 0x000F) * 0x00011)
3845                         );
3846                     break;
3847                 case 6:
3848                     color = (color << 8) + 0xff /* shift left by 8 */ ;
3849                     break;
3850                 case 8:
3851                     break;
3852                 default:
3853                     rrd_set_error("the color format is #RRGGBB[AA]");
3854                     return;
3855                 }
3856                 if ((ci = grc_conv(col_nam)) != -1) {
3857                     im->graph_col[ci] = gfx_hex_to_col(color);
3858                 } else {
3859                     rrd_set_error("invalid color name '%s'", col_nam);
3860                     return;
3861                 }
3862             } else {
3863                 rrd_set_error("invalid color def format");
3864                 return;
3865             }
3866             break;
3867         case 'n':{
3868             char      prop[15];
3869             double    size = 1;
3870             char      font[1024] = "";
3871
3872             if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
3873                 int       sindex, propidx;
3874
3875                 if ((sindex = text_prop_conv(prop)) != -1) {
3876                     for (propidx = sindex; propidx < TEXT_PROP_LAST;
3877                          propidx++) {
3878                         if (size > 0) {
3879                             im->text_prop[propidx].size = size;
3880                         }
3881                         if (strlen(font) > 0) {
3882                             strcpy(im->text_prop[propidx].font, font);
3883                         }
3884                         if (propidx == sindex && sindex != 0)
3885                             break;
3886                     }
3887                 } else {
3888                     rrd_set_error("invalid fonttag '%s'", prop);
3889                     return;
3890                 }
3891             } else {
3892                 rrd_set_error("invalid text property format");
3893                 return;
3894             }
3895             break;
3896         }
3897         case 'm':
3898             im->zoom = atof(optarg);
3899             if (im->zoom <= 0.0) {
3900                 rrd_set_error("zoom factor must be > 0");
3901                 return;
3902             }
3903             break;
3904         case 't':
3905             strncpy(im->title, optarg, 150);
3906             im->title[150] = '\0';
3907             break;
3908
3909         case 'R':
3910             if (strcmp(optarg, "normal") == 0) {
3911                 cairo_font_options_set_antialias(im->font_options,
3912                                                  CAIRO_ANTIALIAS_GRAY);
3913                 cairo_font_options_set_hint_style(im->font_options,
3914                                                   CAIRO_HINT_STYLE_FULL);
3915             } else if (strcmp(optarg, "light") == 0) {
3916                 cairo_font_options_set_antialias(im->font_options,
3917                                                  CAIRO_ANTIALIAS_GRAY);
3918                 cairo_font_options_set_hint_style(im->font_options,
3919                                                   CAIRO_HINT_STYLE_SLIGHT);
3920             } else if (strcmp(optarg, "mono") == 0) {
3921                 cairo_font_options_set_antialias(im->font_options,
3922                                                  CAIRO_ANTIALIAS_NONE);
3923                 cairo_font_options_set_hint_style(im->font_options,
3924                                                   CAIRO_HINT_STYLE_FULL);
3925             } else {
3926                 rrd_set_error("unknown font-render-mode '%s'", optarg);
3927                 return;
3928             }
3929             break;
3930         case 'G':
3931             if (strcmp(optarg, "normal") == 0)
3932                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3933             else if (strcmp(optarg, "mono") == 0)
3934                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
3935             else {
3936                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
3937                 return;
3938             }
3939             break;
3940         case 'B':
3941             /* not supported curently */
3942             break;
3943
3944         case 'W':
3945             strncpy(im->watermark, optarg, 100);
3946             im->watermark[99] = '\0';
3947             break;
3948
3949         case '?':
3950             if (optopt != 0)
3951                 rrd_set_error("unknown option '%c'", optopt);
3952             else
3953                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
3954             return;
3955         }
3956     }
3957
3958     if (optind >= argc) {
3959         rrd_set_error("missing filename");
3960         return;
3961     }
3962
3963     if (im->logarithmic == 1 && im->minval <= 0) {
3964         rrd_set_error
3965             ("for a logarithmic yaxis you must specify a lower-limit > 0");
3966         return;
3967     }
3968
3969     if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
3970         /* error string is set in parsetime.c */
3971         return;
3972     }
3973
3974     if (start_tmp < 3600 * 24 * 365 * 10) {
3975         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
3976                       start_tmp);
3977         return;
3978     }
3979
3980     if (end_tmp < start_tmp) {
3981         rrd_set_error("start (%ld) should be less than end (%ld)",
3982                       start_tmp, end_tmp);
3983         return;
3984     }
3985
3986     im->start = start_tmp;
3987     im->end = end_tmp;
3988     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
3989 }
3990
3991 int rrd_graph_color(
3992     image_desc_t *im,
3993     char *var,
3994     char *err,
3995     int optional)
3996 {
3997     char     *color;
3998     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
3999
4000     color = strstr(var, "#");
4001     if (color == NULL) {
4002         if (optional == 0) {
4003             rrd_set_error("Found no color in %s", err);
4004             return 0;
4005         }
4006         return 0;
4007     } else {
4008         int       n = 0;
4009         char     *rest;
4010         long unsigned int col;
4011
4012         rest = strstr(color, ":");
4013         if (rest != NULL)
4014             n = rest - color;
4015         else
4016             n = strlen(color);
4017
4018         switch (n) {
4019         case 7:
4020             sscanf(color, "#%6lx%n", &col, &n);
4021             col = (col << 8) + 0xff /* shift left by 8 */ ;
4022             if (n != 7)
4023                 rrd_set_error("Color problem in %s", err);
4024             break;
4025         case 9:
4026             sscanf(color, "#%8lx%n", &col, &n);
4027             if (n == 9)
4028                 break;
4029         default:
4030             rrd_set_error("Color problem in %s", err);
4031         }
4032         if (rrd_test_error())
4033             return 0;
4034         gdp->col = gfx_hex_to_col(col);
4035         return n;
4036     }
4037 }
4038
4039
4040 int bad_format(
4041     char *fmt)
4042 {
4043     char     *ptr;
4044     int       n = 0;
4045
4046     ptr = fmt;
4047     while (*ptr != '\0')
4048         if (*ptr++ == '%') {
4049
4050             /* line cannot end with percent char */
4051             if (*ptr == '\0')
4052                 return 1;
4053
4054             /* '%s', '%S' and '%%' are allowed */
4055             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4056                 ptr++;
4057
4058             /* %c is allowed (but use only with vdef!) */
4059             else if (*ptr == 'c') {
4060                 ptr++;
4061                 n = 1;
4062             }
4063
4064             /* or else '% 6.2lf' and such are allowed */
4065             else {
4066                 /* optional padding character */
4067                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4068                     ptr++;
4069
4070                 /* This should take care of 'm.n' with all three optional */
4071                 while (*ptr >= '0' && *ptr <= '9')
4072                     ptr++;
4073                 if (*ptr == '.')
4074                     ptr++;
4075                 while (*ptr >= '0' && *ptr <= '9')
4076                     ptr++;
4077
4078                 /* Either 'le', 'lf' or 'lg' must follow here */
4079                 if (*ptr++ != 'l')
4080                     return 1;
4081                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4082                     ptr++;
4083                 else
4084                     return 1;
4085                 n++;
4086             }
4087         }
4088
4089     return (n != 1);
4090 }
4091
4092
4093 int vdef_parse(
4094     gdes,
4095     str)
4096     struct graph_desc_t *gdes;
4097     const char *const str;
4098 {
4099     /* A VDEF currently is either "func" or "param,func"
4100      * so the parsing is rather simple.  Change if needed.
4101      */
4102     double    param;
4103     char      func[30];
4104     int       n;
4105
4106     n = 0;
4107     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4108     if (n == (int) strlen(str)) {   /* matched */
4109         ;
4110     } else {
4111         n = 0;
4112         sscanf(str, "%29[A-Z]%n", func, &n);
4113         if (n == (int) strlen(str)) {   /* matched */
4114             param = DNAN;
4115         } else {
4116             rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4117                           gdes->vname);
4118             return -1;
4119         }
4120     }
4121     if (!strcmp("PERCENT", func))
4122         gdes->vf.op = VDEF_PERCENT;
4123     else if (!strcmp("MAXIMUM", func))
4124         gdes->vf.op = VDEF_MAXIMUM;
4125     else if (!strcmp("AVERAGE", func))
4126         gdes->vf.op = VDEF_AVERAGE;
4127     else if (!strcmp("MINIMUM", func))
4128         gdes->vf.op = VDEF_MINIMUM;
4129     else if (!strcmp("TOTAL", func))
4130         gdes->vf.op = VDEF_TOTAL;
4131     else if (!strcmp("FIRST", func))
4132         gdes->vf.op = VDEF_FIRST;
4133     else if (!strcmp("LAST", func))
4134         gdes->vf.op = VDEF_LAST;
4135     else if (!strcmp("LSLSLOPE", func))
4136         gdes->vf.op = VDEF_LSLSLOPE;
4137     else if (!strcmp("LSLINT", func))
4138         gdes->vf.op = VDEF_LSLINT;
4139     else if (!strcmp("LSLCORREL", func))
4140         gdes->vf.op = VDEF_LSLCORREL;
4141     else {
4142         rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4143                       gdes->vname);
4144         return -1;
4145     };
4146
4147     switch (gdes->vf.op) {
4148     case VDEF_PERCENT:
4149         if (isnan(param)) { /* no parameter given */
4150             rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4151                           func, gdes->vname);
4152             return -1;
4153         };
4154         if (param >= 0.0 && param <= 100.0) {
4155             gdes->vf.param = param;
4156             gdes->vf.val = DNAN;    /* undefined */
4157             gdes->vf.when = 0;  /* undefined */
4158         } else {
4159             rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4160                           gdes->vname);
4161             return -1;
4162         };
4163         break;
4164     case VDEF_MAXIMUM:
4165     case VDEF_AVERAGE:
4166     case VDEF_MINIMUM:
4167     case VDEF_TOTAL:
4168     case VDEF_FIRST:
4169     case VDEF_LAST:
4170     case VDEF_LSLSLOPE:
4171     case VDEF_LSLINT:
4172     case VDEF_LSLCORREL:
4173         if (isnan(param)) {
4174             gdes->vf.param = DNAN;
4175             gdes->vf.val = DNAN;
4176             gdes->vf.when = 0;
4177         } else {
4178             rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4179                           func, gdes->vname);
4180             return -1;
4181         };
4182         break;
4183     };
4184     return 0;
4185 }
4186
4187
4188 int vdef_calc(
4189     im,
4190     gdi)
4191     image_desc_t *im;
4192     int gdi;
4193 {
4194     graph_desc_t *src, *dst;
4195     rrd_value_t *data;
4196     long      step, steps;
4197
4198     dst = &im->gdes[gdi];
4199     src = &im->gdes[dst->vidx];
4200     data = src->data + src->ds;
4201     steps = (src->end - src->start) / src->step;
4202
4203 #if 0
4204     printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4205            src->end, steps);
4206 #endif
4207
4208     switch (dst->vf.op) {
4209     case VDEF_PERCENT:{
4210         rrd_value_t *array;
4211         int       field;
4212
4213
4214         if ((array = malloc(steps * sizeof(double))) == NULL) {
4215             rrd_set_error("malloc VDEV_PERCENT");
4216             return -1;
4217         }
4218         for (step = 0; step < steps; step++) {
4219             array[step] = data[step * src->ds_cnt];
4220         }
4221         qsort(array, step, sizeof(double), vdef_percent_compar);
4222
4223         field = (steps - 1) * dst->vf.param / 100;
4224         dst->vf.val = array[field];
4225         dst->vf.when = 0;   /* no time component */
4226         free(array);
4227 #if 0
4228         for (step = 0; step < steps; step++)
4229             printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4230                    step == field ? '*' : ' ');
4231 #endif
4232     }
4233         break;
4234     case VDEF_MAXIMUM:
4235         step = 0;
4236         while (step != steps && isnan(data[step * src->ds_cnt]))
4237             step++;
4238         if (step == steps) {
4239             dst->vf.val = DNAN;
4240             dst->vf.when = 0;
4241         } else {
4242             dst->vf.val = data[step * src->ds_cnt];
4243             dst->vf.when = src->start + (step + 1) * src->step;
4244         }
4245         while (step != steps) {
4246             if (finite(data[step * src->ds_cnt])) {
4247                 if (data[step * src->ds_cnt] > dst->vf.val) {
4248                     dst->vf.val = data[step * src->ds_cnt];
4249                     dst->vf.when = src->start + (step + 1) * src->step;
4250                 }
4251             }
4252             step++;
4253         }
4254         break;
4255     case VDEF_TOTAL:
4256     case VDEF_AVERAGE:{
4257         int       cnt = 0;
4258         double    sum = 0.0;
4259
4260         for (step = 0; step < steps; step++) {
4261             if (finite(data[step * src->ds_cnt])) {
4262                 sum += data[step * src->ds_cnt];
4263                 cnt++;
4264             };
4265         }
4266         if (cnt) {
4267             if (dst->vf.op == VDEF_TOTAL) {
4268                 dst->vf.val = sum * src->step;
4269                 dst->vf.when = 0;   /* no time component */
4270             } else {
4271                 dst->vf.val = sum / cnt;
4272                 dst->vf.when = 0;   /* no time component */
4273             };
4274         } else {
4275             dst->vf.val = DNAN;
4276             dst->vf.when = 0;
4277         }
4278     }
4279         break;
4280     case VDEF_MINIMUM:
4281         step = 0;
4282         while (step != steps && isnan(data[step * src->ds_cnt]))
4283             step++;
4284         if (step == steps) {
4285             dst->vf.val = DNAN;
4286             dst->vf.when = 0;
4287         } else {
4288             dst->vf.val = data[step * src->ds_cnt];
4289             dst->vf.when = src->start + (step + 1) * src->step;
4290         }
4291         while (step != steps) {
4292             if (finite(data[step * src->ds_cnt])) {
4293                 if (data[step * src->ds_cnt] < dst->vf.val) {
4294                     dst->vf.val = data[step * src->ds_cnt];
4295                     dst->vf.when = src->start + (step + 1) * src->step;
4296                 }
4297             }
4298             step++;
4299         }
4300         break;
4301     case VDEF_FIRST:
4302         /* The time value returned here is one step before the
4303          * actual time value.  This is the start of the first
4304          * non-NaN interval.
4305          */
4306         step = 0;
4307         while (step != steps && isnan(data[step * src->ds_cnt]))
4308             step++;
4309         if (step == steps) {    /* all entries were NaN */
4310             dst->vf.val = DNAN;
4311             dst->vf.when = 0;
4312         } else {
4313             dst->vf.val = data[step * src->ds_cnt];
4314             dst->vf.when = src->start + step * src->step;
4315         }
4316         break;
4317     case VDEF_LAST:
4318         /* The time value returned here is the
4319          * actual time value.  This is the end of the last
4320          * non-NaN interval.
4321          */
4322         step = steps - 1;
4323         while (step >= 0 && isnan(data[step * src->ds_cnt]))
4324             step--;
4325         if (step < 0) { /* all entries were NaN */
4326             dst->vf.val = DNAN;
4327             dst->vf.when = 0;
4328         } else {
4329             dst->vf.val = data[step * src->ds_cnt];
4330             dst->vf.when = src->start + (step + 1) * src->step;
4331         }
4332         break;
4333     case VDEF_LSLSLOPE:
4334     case VDEF_LSLINT:
4335     case VDEF_LSLCORREL:{
4336         /* Bestfit line by linear least squares method */
4337
4338         int       cnt = 0;
4339         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4340
4341         SUMx = 0;
4342         SUMy = 0;
4343         SUMxy = 0;
4344         SUMxx = 0;
4345         SUMyy = 0;
4346
4347         for (step = 0; step < steps; step++) {
4348             if (finite(data[step * src->ds_cnt])) {
4349                 cnt++;
4350                 SUMx += step;
4351                 SUMxx += step * step;
4352                 SUMxy += step * data[step * src->ds_cnt];
4353                 SUMy += data[step * src->ds_cnt];
4354                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4355             };
4356         }
4357
4358         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4359         y_intercept = (SUMy - slope * SUMx) / cnt;
4360         correl =
4361             (SUMxy -
4362              (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4363                                           (SUMx * SUMx) / cnt) * (SUMyy -
4364                                                                   (SUMy *
4365                                                                    SUMy) /
4366                                                                   cnt));
4367
4368         if (cnt) {
4369             if (dst->vf.op == VDEF_LSLSLOPE) {
4370                 dst->vf.val = slope;
4371                 dst->vf.when = 0;
4372             } else if (dst->vf.op == VDEF_LSLINT) {
4373                 dst->vf.val = y_intercept;
4374                 dst->vf.when = 0;
4375             } else if (dst->vf.op == VDEF_LSLCORREL) {
4376                 dst->vf.val = correl;
4377                 dst->vf.when = 0;
4378             };
4379
4380         } else {
4381             dst->vf.val = DNAN;
4382             dst->vf.when = 0;
4383         }
4384     }
4385         break;
4386     }
4387     return 0;
4388 }
4389
4390 /* NaN < -INF < finite_values < INF */
4391 int vdef_percent_compar(
4392     a,
4393     b)
4394     const void *a, *b;
4395 {
4396     /* Equality is not returned; this doesn't hurt except
4397      * (maybe) for a little performance.
4398      */
4399
4400     /* First catch NaN values. They are smallest */
4401     if (isnan(*(double *) a))
4402         return -1;
4403     if (isnan(*(double *) b))
4404         return 1;
4405
4406     /* NaN doesn't reach this part so INF and -INF are extremes.
4407      * The sign from isinf() is compatible with the sign we return
4408      */
4409     if (isinf(*(double *) a))
4410         return isinf(*(double *) a);
4411     if (isinf(*(double *) b))
4412         return isinf(*(double *) b);
4413
4414     /* If we reach this, both values must be finite */
4415     if (*(double *) a < *(double *) b)
4416         return -1;
4417     else
4418         return 1;
4419 }