make pdf output work
[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->surface =
2932             cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
2933                                      im->yimg * im->zoom);
2934         break;
2935     case IF_EPS:
2936         im->surface =
2937             cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
2938                                     im->yimg * im->zoom);
2939         break;
2940     case IF_SVG:
2941         im->surface =
2942             cairo_svg_surface_create(im->graphfile, im->ximg * im->zoom,
2943                                      im->yimg * im->zoom);
2944         cairo_svg_surface_restrict_to_version(im->surface,
2945                                               CAIRO_SVG_VERSION_1_1);
2946         break;
2947     };
2948     im->cr = cairo_create(im->surface);
2949     pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
2950     cairo_set_antialias(im->cr, im->graph_antialias);
2951     cairo_scale(im->cr, im->zoom, im->zoom);
2952
2953     gfx_new_area(im,
2954                  0, 0,
2955                  0, im->yimg, im->ximg, im->yimg, im->graph_col[GRC_BACK]);
2956
2957     gfx_add_point(im, im->ximg, 0);
2958     gfx_close_path(im);
2959
2960     gfx_new_area(im,
2961                  im->xorigin, im->yorigin,
2962                  im->xorigin + im->xsize, im->yorigin,
2963                  im->xorigin + im->xsize, im->yorigin - im->ysize,
2964                  im->graph_col[GRC_CANVAS]);
2965
2966     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
2967     gfx_close_path(im);
2968
2969     if (im->minval > 0.0)
2970         areazero = im->minval;
2971     if (im->maxval < 0.0)
2972         areazero = im->maxval;
2973
2974     for (i = 0; i < im->gdes_c; i++) {
2975         switch (im->gdes[i].gf) {
2976         case GF_CDEF:
2977         case GF_VDEF:
2978         case GF_DEF:
2979         case GF_PRINT:
2980         case GF_GPRINT:
2981         case GF_COMMENT:
2982         case GF_HRULE:
2983         case GF_VRULE:
2984         case GF_XPORT:
2985         case GF_SHIFT:
2986             break;
2987         case GF_TICK:
2988             for (ii = 0; ii < im->xsize; ii++) {
2989                 if (!isnan(im->gdes[i].p_data[ii]) &&
2990                     im->gdes[i].p_data[ii] != 0.0) {
2991                     if (im->gdes[i].yrule > 0) {
2992                         gfx_line(im,
2993                                  im->xorigin + ii, im->yorigin,
2994                                  im->xorigin + ii,
2995                                  im->yorigin -
2996                                  im->gdes[i].yrule * im->ysize, 1.0,
2997                                  im->gdes[i].col);
2998                     } else if (im->gdes[i].yrule < 0) {
2999                         gfx_line(im,
3000                                  im->xorigin + ii,
3001                                  im->yorigin - im->ysize,
3002                                  im->xorigin + ii,
3003                                  im->yorigin - (1 -
3004                                                 im->gdes[i].yrule) *
3005                                  im->ysize, 1.0, im->gdes[i].col);
3006
3007                     }
3008                 }
3009             }
3010             break;
3011         case GF_LINE:
3012         case GF_AREA:
3013             /* fix data points at oo and -oo */
3014             for (ii = 0; ii < im->xsize; ii++) {
3015                 if (isinf(im->gdes[i].p_data[ii])) {
3016                     if (im->gdes[i].p_data[ii] > 0) {
3017                         im->gdes[i].p_data[ii] = im->maxval;
3018                     } else {
3019                         im->gdes[i].p_data[ii] = im->minval;
3020                     }
3021
3022                 }
3023             }           /* for */
3024
3025             /* *******************************************************
3026                a           ___. (a,t) 
3027                |   |    ___
3028                ____|   |   |   |
3029                |       |___|
3030                -------|--t-1--t--------------------------------      
3031
3032                if we know the value at time t was a then 
3033                we draw a square from t-1 to t with the value a.
3034
3035                ********************************************************* */
3036             if (im->gdes[i].col.alpha != 0.0) {
3037                 /* GF_LINE and friend */
3038                 if (im->gdes[i].gf == GF_LINE) {
3039                     double    last_y = 0.0;
3040                     int       draw_on = 0;
3041
3042                     cairo_save(im->cr);
3043                     cairo_new_path(im->cr);
3044
3045                     cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3046                     for (ii = 1; ii < im->xsize; ii++) {
3047                         if (isnan(im->gdes[i].p_data[ii])
3048                             || (im->slopemode == 1
3049                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3050                             draw_on = 0;
3051                             continue;
3052                         }
3053                         if (draw_on == 0) {
3054                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3055                             if (im->slopemode == 0) {
3056                                 double    x = ii - 1 + im->xorigin;
3057                                 double    y = last_y;
3058
3059                                 gfx_line_fit(im, &x, &y);
3060                                 cairo_move_to(im->cr, x, y);
3061                                 x = ii + im->xorigin;
3062                                 y = last_y;
3063                                 gfx_line_fit(im, &x, &y);
3064                                 cairo_line_to(im->cr, x, y);
3065                             } else {
3066                                 double    x = ii - 1 + im->xorigin;
3067                                 double    y = ytr(im,
3068                                                   im->gdes[i].p_data[ii - 1]);
3069
3070                                 gfx_line_fit(im, &x, &y);
3071                                 cairo_move_to(im->cr, x, y);
3072                                 x = ii + im->xorigin;
3073                                 y = last_y;
3074                                 gfx_line_fit(im, &x, &y);
3075                                 cairo_line_to(im->cr, x, y);
3076                             }
3077                             draw_on = 1;
3078                         } else {
3079                             double    x1 = ii + im->xorigin;
3080                             double    y1 = ytr(im, im->gdes[i].p_data[ii]);
3081
3082                             if (im->slopemode == 0
3083                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3084                                 double    x = ii - 1 + im->xorigin;
3085                                 double    y = y1;
3086
3087                                 gfx_line_fit(im, &x, &y);
3088                                 cairo_line_to(im->cr, x, y);
3089                             };
3090                             last_y = y1;
3091                             gfx_line_fit(im, &x1, &y1);
3092                             cairo_line_to(im->cr, x1, y1);
3093                         };
3094
3095                     }
3096                     cairo_set_source_rgba(im->cr, im->gdes[i].col.red,
3097                                           im->gdes[i].col.green,
3098                                           im->gdes[i].col.blue,
3099                                           im->gdes[i].col.alpha);
3100                     cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3101                     cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3102                     cairo_stroke(im->cr);
3103                     cairo_restore(im->cr);
3104                 } else {
3105                     int       idxI = -1;
3106                     double   *foreY = malloc(sizeof(double) * im->xsize * 2);
3107                     double   *foreX = malloc(sizeof(double) * im->xsize * 2);
3108                     double   *backY = malloc(sizeof(double) * im->xsize * 2);
3109                     double   *backX = malloc(sizeof(double) * im->xsize * 2);
3110                     int       drawem = 0;
3111
3112                     for (ii = 0; ii <= im->xsize; ii++) {
3113                         double    ybase, ytop;
3114
3115                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3116                             int       cntI = 1;
3117                             int       lastI = 0;
3118
3119                             while (cntI < idxI
3120                                    && AlmostEqual2sComplement(foreY[lastI],
3121                                                               foreY[cntI], 4)
3122                                    && AlmostEqual2sComplement(foreY[lastI],
3123                                                               foreY[cntI + 1],
3124                                                               4)) {
3125                                 cntI++;
3126                             }
3127                             gfx_new_area(im,
3128                                          backX[0], backY[0],
3129                                          foreX[0], foreY[0],
3130                                          foreX[cntI], foreY[cntI],
3131                                          im->gdes[i].col);
3132                             while (cntI < idxI) {
3133                                 lastI = cntI;
3134                                 cntI++;
3135                                 while (cntI < idxI
3136                                        &&
3137                                        AlmostEqual2sComplement(foreY[lastI],
3138                                                                foreY[cntI], 4)
3139                                        &&
3140                                        AlmostEqual2sComplement(foreY[lastI],
3141                                                                foreY[cntI +
3142                                                                      1], 4)) {
3143                                     cntI++;
3144                                 }
3145                                 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3146                             }
3147                             gfx_add_point(im, backX[idxI], backY[idxI]);
3148                             while (idxI > 1) {
3149                                 lastI = idxI;
3150                                 idxI--;
3151                                 while (idxI > 1
3152                                        &&
3153                                        AlmostEqual2sComplement(backY[lastI],
3154                                                                backY[idxI], 4)
3155                                        &&
3156                                        AlmostEqual2sComplement(backY[lastI],
3157                                                                backY[idxI -
3158                                                                      1], 4)) {
3159                                     idxI--;
3160                                 }
3161                                 gfx_add_point(im, backX[idxI], backY[idxI]);
3162                             }
3163                             idxI = -1;
3164                             drawem = 0;
3165                             gfx_close_path(im);
3166                         }
3167                         if (drawem != 0) {
3168                             drawem = 0;
3169                             idxI = -1;
3170                         }
3171                         if (ii == im->xsize)
3172                             break;
3173
3174                         if (im->slopemode == 0 && ii == 0) {
3175                             continue;
3176                         }
3177                         if (isnan(im->gdes[i].p_data[ii])) {
3178                             drawem = 1;
3179                             continue;
3180                         }
3181                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3182                         if (lastgdes && im->gdes[i].stack) {
3183                             ybase = ytr(im, lastgdes->p_data[ii]);
3184                         } else {
3185                             ybase = ytr(im, areazero);
3186                         }
3187                         if (ybase == ytop) {
3188                             drawem = 1;
3189                             continue;
3190                         }
3191
3192                         if (ybase > ytop) {
3193                             double    extra = ytop;
3194
3195                             ytop = ybase;
3196                             ybase = extra;
3197                         }
3198                         if (im->slopemode == 0) {
3199                             backY[++idxI] = ybase - 0.2;
3200                             backX[idxI] = ii + im->xorigin - 1;
3201                             foreY[idxI] = ytop + 0.2;
3202                             foreX[idxI] = ii + im->xorigin - 1;
3203                         }
3204                         backY[++idxI] = ybase - 0.2;
3205                         backX[idxI] = ii + im->xorigin;
3206                         foreY[idxI] = ytop + 0.2;
3207                         foreX[idxI] = ii + im->xorigin;
3208                     }
3209                     /* close up any remaining area */
3210                     free(foreY);
3211                     free(foreX);
3212                     free(backY);
3213                     free(backX);
3214                 }       /* else GF_LINE */
3215             }
3216             /* if color != 0x0 */
3217             /* make sure we do not run into trouble when stacking on NaN */
3218             for (ii = 0; ii < im->xsize; ii++) {
3219                 if (isnan(im->gdes[i].p_data[ii])) {
3220                     if (lastgdes && (im->gdes[i].stack)) {
3221                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3222                     } else {
3223                         im->gdes[i].p_data[ii] = areazero;
3224                     }
3225                 }
3226             }
3227             lastgdes = &(im->gdes[i]);
3228             break;
3229         case GF_STACK:
3230             rrd_set_error
3231                 ("STACK should already be turned into LINE or AREA here");
3232             return -1;
3233             break;
3234
3235         }               /* switch */
3236     }
3237
3238     /* grid_paint also does the text */
3239     if (!(im->extra_flags & ONLY_GRAPH))
3240         grid_paint(im);
3241
3242
3243     if (!(im->extra_flags & ONLY_GRAPH))
3244         axis_paint(im);
3245
3246     /* the RULES are the last thing to paint ... */
3247     for (i = 0; i < im->gdes_c; i++) {
3248
3249         switch (im->gdes[i].gf) {
3250         case GF_HRULE:
3251             if (im->gdes[i].yrule >= im->minval
3252                 && im->gdes[i].yrule <= im->maxval)
3253                 gfx_line(im,
3254                          im->xorigin, ytr(im, im->gdes[i].yrule),
3255                          im->xorigin + im->xsize, ytr(im,
3256                                                       im->gdes[i].yrule),
3257                          1.0, im->gdes[i].col);
3258             break;
3259         case GF_VRULE:
3260             if (im->gdes[i].xrule >= im->start
3261                 && im->gdes[i].xrule <= im->end)
3262                 gfx_line(im,
3263                          xtr(im, im->gdes[i].xrule), im->yorigin,
3264                          xtr(im, im->gdes[i].xrule),
3265                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3266             break;
3267         default:
3268             break;
3269         }
3270     }
3271
3272
3273     switch (im->imgformat) {
3274     case IF_PNG:
3275         if (cairo_surface_write_to_png(im->surface, im->graphfile) !=
3276             CAIRO_STATUS_SUCCESS) {
3277             rrd_set_error("Could not save png to '%s'", im->graphfile);
3278             return 1;
3279         }
3280         break;
3281     default:
3282         cairo_show_page(im->cr);
3283         break;
3284     }
3285     return 0;
3286 }
3287
3288
3289 /*****************************************************
3290  * graph stuff 
3291  *****************************************************/
3292
3293 int gdes_alloc(
3294     image_desc_t *im)
3295 {
3296
3297     im->gdes_c++;
3298     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3299                                                  * sizeof(graph_desc_t))) ==
3300         NULL) {
3301         rrd_set_error("realloc graph_descs");
3302         return -1;
3303     }
3304
3305
3306     im->gdes[im->gdes_c - 1].step = im->step;
3307     im->gdes[im->gdes_c - 1].step_orig = im->step;
3308     im->gdes[im->gdes_c - 1].stack = 0;
3309     im->gdes[im->gdes_c - 1].linewidth = 0;
3310     im->gdes[im->gdes_c - 1].debug = 0;
3311     im->gdes[im->gdes_c - 1].start = im->start;
3312     im->gdes[im->gdes_c - 1].start_orig = im->start;
3313     im->gdes[im->gdes_c - 1].end = im->end;
3314     im->gdes[im->gdes_c - 1].end_orig = im->end;
3315     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3316     im->gdes[im->gdes_c - 1].data = NULL;
3317     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3318     im->gdes[im->gdes_c - 1].data_first = 0;
3319     im->gdes[im->gdes_c - 1].p_data = NULL;
3320     im->gdes[im->gdes_c - 1].rpnp = NULL;
3321     im->gdes[im->gdes_c - 1].shift = 0.0;
3322     im->gdes[im->gdes_c - 1].col.red = 0.0;
3323     im->gdes[im->gdes_c - 1].col.green = 0.0;
3324     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3325     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3326     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3327     im->gdes[im->gdes_c - 1].format[0] = '\0';
3328     im->gdes[im->gdes_c - 1].strftm = 0;
3329     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3330     im->gdes[im->gdes_c - 1].ds = -1;
3331     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3332     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3333     im->gdes[im->gdes_c - 1].p_data = NULL;
3334     im->gdes[im->gdes_c - 1].yrule = DNAN;
3335     im->gdes[im->gdes_c - 1].xrule = 0;
3336     return 0;
3337 }
3338
3339 /* copies input untill the first unescaped colon is found
3340    or until input ends. backslashes have to be escaped as well */
3341 int scan_for_col(
3342     const char *const input,
3343     int len,
3344     char *const output)
3345 {
3346     int       inp, outp = 0;
3347
3348     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3349         if (input[inp] == '\\' &&
3350             input[inp + 1] != '\0' &&
3351             (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3352             output[outp++] = input[++inp];
3353         } else {
3354             output[outp++] = input[inp];
3355         }
3356     }
3357     output[outp] = '\0';
3358     return inp;
3359 }
3360
3361 /* Some surgery done on this function, it became ridiculously big.
3362 ** Things moved:
3363 ** - initializing     now in rrd_graph_init()
3364 ** - options parsing  now in rrd_graph_options()
3365 ** - script parsing   now in rrd_graph_script()
3366 */
3367 int rrd_graph(
3368     int argc,
3369     char **argv,
3370     char ***prdata,
3371     int *xsize,
3372     int *ysize,
3373     FILE * stream,
3374     double *ymin,
3375     double *ymax)
3376 {
3377     image_desc_t im;
3378
3379     rrd_graph_init(&im);
3380
3381     /* a dummy surface so that we can measure text sizes for placements */
3382     im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3383     im.cr = cairo_create(im.surface);
3384
3385
3386     /* not currently using this ... */
3387     im.graphhandle = stream;
3388
3389     rrd_graph_options(argc, argv, &im);
3390     if (rrd_test_error()) {
3391         im_free(&im);
3392         return -1;
3393     }
3394
3395     if (strlen(argv[optind]) >= MAXPATH) {
3396         rrd_set_error("filename (including path) too long");
3397         im_free(&im);
3398         return -1;
3399     }
3400     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3401     im.graphfile[MAXPATH - 1] = '\0';
3402
3403     rrd_graph_script(argc, argv, &im, 1);
3404     if (rrd_test_error()) {
3405         im_free(&im);
3406         return -1;
3407     }
3408
3409     /* Everything is now read and the actual work can start */
3410
3411     (*prdata) = NULL;
3412     if (graph_paint(&im, prdata) == -1) {
3413         im_free(&im);
3414         return -1;
3415     }
3416
3417     /* The image is generated and needs to be output.
3418      ** Also, if needed, print a line with information about the image.
3419      */
3420
3421     *xsize = im.ximg;
3422     *ysize = im.yimg;
3423     *ymin = im.minval;
3424     *ymax = im.maxval;
3425     if (im.imginfo) {
3426         char     *filename;
3427
3428         if (!(*prdata)) {
3429             /* maybe prdata is not allocated yet ... lets do it now */
3430             if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3431                 rrd_set_error("malloc imginfo");
3432                 return -1;
3433             };
3434         }
3435         if (((*prdata)[0] =
3436              malloc((strlen(im.imginfo) + 200 +
3437                      strlen(im.graphfile)) * sizeof(char)))
3438             == NULL) {
3439             rrd_set_error("malloc imginfo");
3440             return -1;
3441         }
3442         filename = im.graphfile + strlen(im.graphfile);
3443         while (filename > im.graphfile) {
3444             if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3445                 break;
3446             filename--;
3447         }
3448
3449         sprintf((*prdata)[0], im.imginfo, filename,
3450                 (long) (im.zoom * im.ximg), (long) (im.zoom * im.yimg));
3451     }
3452     im_free(&im);
3453     return 0;
3454 }
3455
3456 void rrd_graph_init(
3457     image_desc_t *im)
3458 {
3459     unsigned int i;
3460
3461 #ifdef HAVE_TZSET
3462     tzset();
3463 #endif
3464 #ifdef HAVE_SETLOCALE
3465     setlocale(LC_TIME, "");
3466 #ifdef HAVE_MBSTOWCS
3467     setlocale(LC_CTYPE, "");
3468 #endif
3469 #endif
3470     im->yorigin = 0;
3471     im->xorigin = 0;
3472     im->minval = 0;
3473     im->xlab_user.minsec = -1;
3474     im->ximg = 0;
3475     im->yimg = 0;
3476     im->xsize = 400;
3477     im->ysize = 100;
3478     im->step = 0;
3479     im->ylegend[0] = '\0';
3480     im->title[0] = '\0';
3481     im->watermark[0] = '\0';
3482     im->minval = DNAN;
3483     im->maxval = DNAN;
3484     im->unitsexponent = 9999;
3485     im->unitslength = 6;
3486     im->forceleftspace = 0;
3487     im->symbol = ' ';
3488     im->viewfactor = 1.0;
3489     im->imgformat = IF_PNG;
3490     im->cr = NULL;
3491     im->surface = NULL;
3492     im->extra_flags = 0;
3493     im->rigid = 0;
3494     im->gridfit = 1;
3495     im->imginfo = NULL;
3496     im->lazy = 0;
3497     im->slopemode = 0;
3498     im->logarithmic = 0;
3499     im->ygridstep = DNAN;
3500     im->draw_x_grid = 1;
3501     im->draw_y_grid = 1;
3502     im->base = 1000;
3503     im->prt_c = 0;
3504     im->gdes_c = 0;
3505     im->gdes = NULL;
3506     im->grid_dash_on = 1;
3507     im->grid_dash_off = 1;
3508     im->tabwidth = 40.0;
3509     im->zoom = 1;
3510     im->font_options = cairo_font_options_create();
3511     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3512
3513     cairo_font_options_set_hint_style(im->font_options,
3514                                       CAIRO_HINT_STYLE_FULL);
3515     cairo_font_options_set_hint_metrics(im->font_options,
3516                                         CAIRO_HINT_METRICS_ON);
3517     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3518
3519
3520     for (i = 0; i < DIM(graph_col); i++)
3521         im->graph_col[i] = graph_col[i];
3522
3523 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3524     {
3525         char     *windir;
3526         char      rrd_win_default_font[1000];
3527
3528         windir = getenv("windir");
3529         /* %windir% is something like D:\windows or C:\winnt */
3530         if (windir != NULL) {
3531             strncpy(rrd_win_default_font, windir, 500);
3532             rrd_win_default_font[500] = '\0';
3533             strcat(rrd_win_default_font, "\\fonts\\");
3534             strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3535             for (i = 0; i < DIM(text_prop); i++) {
3536                 strncpy(text_prop[i].font, rrd_win_default_font,
3537                         sizeof(text_prop[i].font) - 1);
3538                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3539             }
3540         }
3541     }
3542 #endif
3543     {
3544         char     *deffont;
3545
3546         deffont = getenv("RRD_DEFAULT_FONT");
3547         if (deffont != NULL) {
3548             for (i = 0; i < DIM(text_prop); i++) {
3549                 strncpy(text_prop[i].font, deffont,
3550                         sizeof(text_prop[i].font) - 1);
3551                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3552             }
3553         }
3554     }
3555     for (i = 0; i < DIM(text_prop); i++) {
3556         im->text_prop[i].size = text_prop[i].size;
3557         strcpy(im->text_prop[i].font, text_prop[i].font);
3558     }
3559 }
3560
3561 void rrd_graph_options(
3562     int argc,
3563     char *argv[],
3564     image_desc_t *im)
3565 {
3566     int       stroff;
3567     char     *parsetime_error = NULL;
3568     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3569     time_t    start_tmp = 0, end_tmp = 0;
3570     long      long_tmp;
3571     struct rrd_time_value start_tv, end_tv;
3572     long unsigned int color;
3573
3574     optind = 0;
3575     opterr = 0;         /* initialize getopt */
3576
3577     parsetime("end-24h", &start_tv);
3578     parsetime("now", &end_tv);
3579
3580     /* defines for long options without a short equivalent. should be bytes,
3581        and may not collide with (the ASCII value of) short options */
3582 #define LONGOPT_UNITS_SI 255
3583
3584     while (1) {
3585         static struct option long_options[] = {
3586             {"start", required_argument, 0, 's'},
3587             {"end", required_argument, 0, 'e'},
3588             {"x-grid", required_argument, 0, 'x'},
3589             {"y-grid", required_argument, 0, 'y'},
3590             {"vertical-label", required_argument, 0, 'v'},
3591             {"width", required_argument, 0, 'w'},
3592             {"height", required_argument, 0, 'h'},
3593             {"full-size-mode", no_argument, 0, 'D'},
3594             {"interlaced", no_argument, 0, 'i'},
3595             {"upper-limit", required_argument, 0, 'u'},
3596             {"lower-limit", required_argument, 0, 'l'},
3597             {"rigid", no_argument, 0, 'r'},
3598             {"base", required_argument, 0, 'b'},
3599             {"logarithmic", no_argument, 0, 'o'},
3600             {"color", required_argument, 0, 'c'},
3601             {"font", required_argument, 0, 'n'},
3602             {"title", required_argument, 0, 't'},
3603             {"imginfo", required_argument, 0, 'f'},
3604             {"imgformat", required_argument, 0, 'a'},
3605             {"lazy", no_argument, 0, 'z'},
3606             {"zoom", required_argument, 0, 'm'},
3607             {"no-legend", no_argument, 0, 'g'},
3608             {"force-rules-legend", no_argument, 0, 'F'},
3609             {"only-graph", no_argument, 0, 'j'},
3610             {"alt-y-grid", no_argument, 0, 'Y'},
3611             {"no-minor", no_argument, 0, 'I'},
3612             {"slope-mode", no_argument, 0, 'E'},
3613             {"alt-autoscale", no_argument, 0, 'A'},
3614             {"alt-autoscale-min", no_argument, 0, 'J'},
3615             {"alt-autoscale-max", no_argument, 0, 'M'},
3616             {"no-gridfit", no_argument, 0, 'N'},
3617             {"units-exponent", required_argument, 0, 'X'},
3618             {"units-length", required_argument, 0, 'L'},
3619             {"units", required_argument, 0, LONGOPT_UNITS_SI},
3620             {"step", required_argument, 0, 'S'},
3621             {"tabwidth", required_argument, 0, 'T'},
3622             {"font-render-mode", required_argument, 0, 'R'},
3623             {"graph-render-mode", required_argument, 0, 'G'},
3624             {"font-smoothing-threshold", required_argument, 0, 'B'},
3625             {"watermark", required_argument, 0, 'W'},
3626             {"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 */
3627             {0, 0, 0, 0}
3628         };
3629         int       option_index = 0;
3630         int       opt;
3631         int       col_start, col_end;
3632
3633         opt = getopt_long(argc, argv,
3634                           "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:",
3635                           long_options, &option_index);
3636
3637         if (opt == EOF)
3638             break;
3639
3640         switch (opt) {
3641         case 'I':
3642             im->extra_flags |= NOMINOR;
3643             break;
3644         case 'Y':
3645             im->extra_flags |= ALTYGRID;
3646             break;
3647         case 'A':
3648             im->extra_flags |= ALTAUTOSCALE;
3649             break;
3650         case 'J':
3651             im->extra_flags |= ALTAUTOSCALE_MIN;
3652             break;
3653         case 'M':
3654             im->extra_flags |= ALTAUTOSCALE_MAX;
3655             break;
3656         case 'j':
3657             im->extra_flags |= ONLY_GRAPH;
3658             break;
3659         case 'g':
3660             im->extra_flags |= NOLEGEND;
3661             break;
3662         case 'F':
3663             im->extra_flags |= FORCE_RULES_LEGEND;
3664             break;
3665         case LONGOPT_UNITS_SI:
3666             if (im->extra_flags & FORCE_UNITS) {
3667                 rrd_set_error("--units can only be used once!");
3668                 return;
3669             }
3670             if (strcmp(optarg, "si") == 0)
3671                 im->extra_flags |= FORCE_UNITS_SI;
3672             else {
3673                 rrd_set_error("invalid argument for --units: %s", optarg);
3674                 return;
3675             }
3676             break;
3677         case 'X':
3678             im->unitsexponent = atoi(optarg);
3679             break;
3680         case 'L':
3681             im->unitslength = atoi(optarg);
3682             im->forceleftspace = 1;
3683             break;
3684         case 'T':
3685             im->tabwidth = atof(optarg);
3686             break;
3687         case 'S':
3688             im->step = atoi(optarg);
3689             break;
3690         case 'N':
3691             im->gridfit = 0;
3692             break;
3693         case 's':
3694             if ((parsetime_error = parsetime(optarg, &start_tv))) {
3695                 rrd_set_error("start time: %s", parsetime_error);
3696                 return;
3697             }
3698             break;
3699         case 'e':
3700             if ((parsetime_error = parsetime(optarg, &end_tv))) {
3701                 rrd_set_error("end time: %s", parsetime_error);
3702                 return;
3703             }
3704             break;
3705         case 'x':
3706             if (strcmp(optarg, "none") == 0) {
3707                 im->draw_x_grid = 0;
3708                 break;
3709             };
3710
3711             if (sscanf(optarg,
3712                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3713                        scan_gtm,
3714                        &im->xlab_user.gridst,
3715                        scan_mtm,
3716                        &im->xlab_user.mgridst,
3717                        scan_ltm,
3718                        &im->xlab_user.labst,
3719                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3720                 strncpy(im->xlab_form, optarg + stroff,
3721                         sizeof(im->xlab_form) - 1);
3722                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3723                 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3724                     rrd_set_error("unknown keyword %s", scan_gtm);
3725                     return;
3726                 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3727                            == -1) {
3728                     rrd_set_error("unknown keyword %s", scan_mtm);
3729                     return;
3730                 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3731                            -1) {
3732                     rrd_set_error("unknown keyword %s", scan_ltm);
3733                     return;
3734                 }
3735                 im->xlab_user.minsec = 1;
3736                 im->xlab_user.stst = im->xlab_form;
3737             } else {
3738                 rrd_set_error("invalid x-grid format");
3739                 return;
3740             }
3741             break;
3742         case 'y':
3743
3744             if (strcmp(optarg, "none") == 0) {
3745                 im->draw_y_grid = 0;
3746                 break;
3747             };
3748
3749             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3750                 if (im->ygridstep <= 0) {
3751                     rrd_set_error("grid step must be > 0");
3752                     return;
3753                 } else if (im->ylabfact < 1) {
3754                     rrd_set_error("label factor must be > 0");
3755                     return;
3756                 }
3757             } else {
3758                 rrd_set_error("invalid y-grid format");
3759                 return;
3760             }
3761             break;
3762         case 'v':
3763             strncpy(im->ylegend, optarg, 150);
3764             im->ylegend[150] = '\0';
3765             break;
3766         case 'u':
3767             im->maxval = atof(optarg);
3768             break;
3769         case 'l':
3770             im->minval = atof(optarg);
3771             break;
3772         case 'b':
3773             im->base = atol(optarg);
3774             if (im->base != 1024 && im->base != 1000) {
3775                 rrd_set_error
3776                     ("the only sensible value for base apart from 1000 is 1024");
3777                 return;
3778             }
3779             break;
3780         case 'w':
3781             long_tmp = atol(optarg);
3782             if (long_tmp < 10) {
3783                 rrd_set_error("width below 10 pixels");
3784                 return;
3785             }
3786             im->xsize = long_tmp;
3787             break;
3788         case 'h':
3789             long_tmp = atol(optarg);
3790             if (long_tmp < 10) {
3791                 rrd_set_error("height below 10 pixels");
3792                 return;
3793             }
3794             im->ysize = long_tmp;
3795             break;
3796         case 'D':
3797             im->extra_flags |= FULL_SIZE_MODE;
3798             break;
3799         case 'i':
3800             /* interlaced png not supported at the moment */
3801             break;
3802         case 'r':
3803             im->rigid = 1;
3804             break;
3805         case 'f':
3806             im->imginfo = optarg;
3807             break;
3808         case 'a':
3809             if ((int) (im->imgformat = if_conv(optarg)) == -1) {
3810                 rrd_set_error("unsupported graphics format '%s'", optarg);
3811                 return;
3812             }
3813             break;
3814         case 'z':
3815             im->lazy = 1;
3816             break;
3817         case 'E':
3818             im->slopemode = 1;
3819             break;
3820
3821         case 'o':
3822             im->logarithmic = 1;
3823             break;
3824         case 'c':
3825             if (sscanf(optarg,
3826                        "%10[A-Z]#%n%8lx%n",
3827                        col_nam, &col_start, &color, &col_end) == 2) {
3828                 int       ci;
3829                 int       col_len = col_end - col_start;
3830
3831                 switch (col_len) {
3832                 case 3:
3833                     color = (((color & 0xF00) * 0x110000) |
3834                              ((color & 0x0F0) * 0x011000) |
3835                              ((color & 0x00F) * 0x001100) | 0x000000FF);
3836                     break;
3837                 case 4:
3838                     color = (((color & 0xF000) * 0x11000) |
3839                              ((color & 0x0F00) * 0x01100) |
3840                              ((color & 0x00F0) * 0x00110) |
3841                              ((color & 0x000F) * 0x00011)
3842                         );
3843                     break;
3844                 case 6:
3845                     color = (color << 8) + 0xff /* shift left by 8 */ ;
3846                     break;
3847                 case 8:
3848                     break;
3849                 default:
3850                     rrd_set_error("the color format is #RRGGBB[AA]");
3851                     return;
3852                 }
3853                 if ((ci = grc_conv(col_nam)) != -1) {
3854                     im->graph_col[ci] = gfx_hex_to_col(color);
3855                 } else {
3856                     rrd_set_error("invalid color name '%s'", col_nam);
3857                     return;
3858                 }
3859             } else {
3860                 rrd_set_error("invalid color def format");
3861                 return;
3862             }
3863             break;
3864         case 'n':{
3865             char      prop[15];
3866             double    size = 1;
3867             char      font[1024] = "";
3868
3869             if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
3870                 int       sindex, propidx;
3871
3872                 if ((sindex = text_prop_conv(prop)) != -1) {
3873                     for (propidx = sindex; propidx < TEXT_PROP_LAST;
3874                          propidx++) {
3875                         if (size > 0) {
3876                             im->text_prop[propidx].size = size;
3877                         }
3878                         if (strlen(font) > 0) {
3879                             strcpy(im->text_prop[propidx].font, font);
3880                         }
3881                         if (propidx == sindex && sindex != 0)
3882                             break;
3883                     }
3884                 } else {
3885                     rrd_set_error("invalid fonttag '%s'", prop);
3886                     return;
3887                 }
3888             } else {
3889                 rrd_set_error("invalid text property format");
3890                 return;
3891             }
3892             break;
3893         }
3894         case 'm':
3895             im->zoom = atof(optarg);
3896             if (im->zoom <= 0.0) {
3897                 rrd_set_error("zoom factor must be > 0");
3898                 return;
3899             }
3900             break;
3901         case 't':
3902             strncpy(im->title, optarg, 150);
3903             im->title[150] = '\0';
3904             break;
3905
3906         case 'R':
3907             if (strcmp(optarg, "normal") == 0) {
3908                 cairo_font_options_set_antialias(im->font_options,
3909                                                  CAIRO_ANTIALIAS_GRAY);
3910                 cairo_font_options_set_hint_style(im->font_options,
3911                                                   CAIRO_HINT_STYLE_FULL);
3912             } else if (strcmp(optarg, "light") == 0) {
3913                 cairo_font_options_set_antialias(im->font_options,
3914                                                  CAIRO_ANTIALIAS_GRAY);
3915                 cairo_font_options_set_hint_style(im->font_options,
3916                                                   CAIRO_HINT_STYLE_SLIGHT);
3917             } else if (strcmp(optarg, "mono") == 0) {
3918                 cairo_font_options_set_antialias(im->font_options,
3919                                                  CAIRO_ANTIALIAS_NONE);
3920                 cairo_font_options_set_hint_style(im->font_options,
3921                                                   CAIRO_HINT_STYLE_FULL);
3922             } else {
3923                 rrd_set_error("unknown font-render-mode '%s'", optarg);
3924                 return;
3925             }
3926             break;
3927         case 'G':
3928             if (strcmp(optarg, "normal") == 0)
3929                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3930             else if (strcmp(optarg, "mono") == 0)
3931                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
3932             else {
3933                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
3934                 return;
3935             }
3936             break;
3937         case 'B':
3938             /* not supported curently */
3939             break;
3940
3941         case 'W':
3942             strncpy(im->watermark, optarg, 100);
3943             im->watermark[99] = '\0';
3944             break;
3945
3946         case '?':
3947             if (optopt != 0)
3948                 rrd_set_error("unknown option '%c'", optopt);
3949             else
3950                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
3951             return;
3952         }
3953     }
3954
3955     if (optind >= argc) {
3956         rrd_set_error("missing filename");
3957         return;
3958     }
3959
3960     if (im->logarithmic == 1 && im->minval <= 0) {
3961         rrd_set_error
3962             ("for a logarithmic yaxis you must specify a lower-limit > 0");
3963         return;
3964     }
3965
3966     if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
3967         /* error string is set in parsetime.c */
3968         return;
3969     }
3970
3971     if (start_tmp < 3600 * 24 * 365 * 10) {
3972         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
3973                       start_tmp);
3974         return;
3975     }
3976
3977     if (end_tmp < start_tmp) {
3978         rrd_set_error("start (%ld) should be less than end (%ld)",
3979                       start_tmp, end_tmp);
3980         return;
3981     }
3982
3983     im->start = start_tmp;
3984     im->end = end_tmp;
3985     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
3986 }
3987
3988 int rrd_graph_color(
3989     image_desc_t *im,
3990     char *var,
3991     char *err,
3992     int optional)
3993 {
3994     char     *color;
3995     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
3996
3997     color = strstr(var, "#");
3998     if (color == NULL) {
3999         if (optional == 0) {
4000             rrd_set_error("Found no color in %s", err);
4001             return 0;
4002         }
4003         return 0;
4004     } else {
4005         int       n = 0;
4006         char     *rest;
4007         long unsigned int col;
4008
4009         rest = strstr(color, ":");
4010         if (rest != NULL)
4011             n = rest - color;
4012         else
4013             n = strlen(color);
4014
4015         switch (n) {
4016         case 7:
4017             sscanf(color, "#%6lx%n", &col, &n);
4018             col = (col << 8) + 0xff /* shift left by 8 */ ;
4019             if (n != 7)
4020                 rrd_set_error("Color problem in %s", err);
4021             break;
4022         case 9:
4023             sscanf(color, "#%8lx%n", &col, &n);
4024             if (n == 9)
4025                 break;
4026         default:
4027             rrd_set_error("Color problem in %s", err);
4028         }
4029         if (rrd_test_error())
4030             return 0;
4031         gdp->col = gfx_hex_to_col(col);
4032         return n;
4033     }
4034 }
4035
4036
4037 int bad_format(
4038     char *fmt)
4039 {
4040     char     *ptr;
4041     int       n = 0;
4042
4043     ptr = fmt;
4044     while (*ptr != '\0')
4045         if (*ptr++ == '%') {
4046
4047             /* line cannot end with percent char */
4048             if (*ptr == '\0')
4049                 return 1;
4050
4051             /* '%s', '%S' and '%%' are allowed */
4052             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4053                 ptr++;
4054
4055             /* %c is allowed (but use only with vdef!) */
4056             else if (*ptr == 'c') {
4057                 ptr++;
4058                 n = 1;
4059             }
4060
4061             /* or else '% 6.2lf' and such are allowed */
4062             else {
4063                 /* optional padding character */
4064                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4065                     ptr++;
4066
4067                 /* This should take care of 'm.n' with all three optional */
4068                 while (*ptr >= '0' && *ptr <= '9')
4069                     ptr++;
4070                 if (*ptr == '.')
4071                     ptr++;
4072                 while (*ptr >= '0' && *ptr <= '9')
4073                     ptr++;
4074
4075                 /* Either 'le', 'lf' or 'lg' must follow here */
4076                 if (*ptr++ != 'l')
4077                     return 1;
4078                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4079                     ptr++;
4080                 else
4081                     return 1;
4082                 n++;
4083             }
4084         }
4085
4086     return (n != 1);
4087 }
4088
4089
4090 int vdef_parse(
4091     gdes,
4092     str)
4093     struct graph_desc_t *gdes;
4094     const char *const str;
4095 {
4096     /* A VDEF currently is either "func" or "param,func"
4097      * so the parsing is rather simple.  Change if needed.
4098      */
4099     double    param;
4100     char      func[30];
4101     int       n;
4102
4103     n = 0;
4104     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4105     if (n == (int) strlen(str)) {   /* matched */
4106         ;
4107     } else {
4108         n = 0;
4109         sscanf(str, "%29[A-Z]%n", func, &n);
4110         if (n == (int) strlen(str)) {   /* matched */
4111             param = DNAN;
4112         } else {
4113             rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4114                           gdes->vname);
4115             return -1;
4116         }
4117     }
4118     if (!strcmp("PERCENT", func))
4119         gdes->vf.op = VDEF_PERCENT;
4120     else if (!strcmp("MAXIMUM", func))
4121         gdes->vf.op = VDEF_MAXIMUM;
4122     else if (!strcmp("AVERAGE", func))
4123         gdes->vf.op = VDEF_AVERAGE;
4124     else if (!strcmp("MINIMUM", func))
4125         gdes->vf.op = VDEF_MINIMUM;
4126     else if (!strcmp("TOTAL", func))
4127         gdes->vf.op = VDEF_TOTAL;
4128     else if (!strcmp("FIRST", func))
4129         gdes->vf.op = VDEF_FIRST;
4130     else if (!strcmp("LAST", func))
4131         gdes->vf.op = VDEF_LAST;
4132     else if (!strcmp("LSLSLOPE", func))
4133         gdes->vf.op = VDEF_LSLSLOPE;
4134     else if (!strcmp("LSLINT", func))
4135         gdes->vf.op = VDEF_LSLINT;
4136     else if (!strcmp("LSLCORREL", func))
4137         gdes->vf.op = VDEF_LSLCORREL;
4138     else {
4139         rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4140                       gdes->vname);
4141         return -1;
4142     };
4143
4144     switch (gdes->vf.op) {
4145     case VDEF_PERCENT:
4146         if (isnan(param)) { /* no parameter given */
4147             rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4148                           func, gdes->vname);
4149             return -1;
4150         };
4151         if (param >= 0.0 && param <= 100.0) {
4152             gdes->vf.param = param;
4153             gdes->vf.val = DNAN;    /* undefined */
4154             gdes->vf.when = 0;  /* undefined */
4155         } else {
4156             rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4157                           gdes->vname);
4158             return -1;
4159         };
4160         break;
4161     case VDEF_MAXIMUM:
4162     case VDEF_AVERAGE:
4163     case VDEF_MINIMUM:
4164     case VDEF_TOTAL:
4165     case VDEF_FIRST:
4166     case VDEF_LAST:
4167     case VDEF_LSLSLOPE:
4168     case VDEF_LSLINT:
4169     case VDEF_LSLCORREL:
4170         if (isnan(param)) {
4171             gdes->vf.param = DNAN;
4172             gdes->vf.val = DNAN;
4173             gdes->vf.when = 0;
4174         } else {
4175             rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4176                           func, gdes->vname);
4177             return -1;
4178         };
4179         break;
4180     };
4181     return 0;
4182 }
4183
4184
4185 int vdef_calc(
4186     im,
4187     gdi)
4188     image_desc_t *im;
4189     int gdi;
4190 {
4191     graph_desc_t *src, *dst;
4192     rrd_value_t *data;
4193     long      step, steps;
4194
4195     dst = &im->gdes[gdi];
4196     src = &im->gdes[dst->vidx];
4197     data = src->data + src->ds;
4198     steps = (src->end - src->start) / src->step;
4199
4200 #if 0
4201     printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4202            src->end, steps);
4203 #endif
4204
4205     switch (dst->vf.op) {
4206     case VDEF_PERCENT:{
4207         rrd_value_t *array;
4208         int       field;
4209
4210
4211         if ((array = malloc(steps * sizeof(double))) == NULL) {
4212             rrd_set_error("malloc VDEV_PERCENT");
4213             return -1;
4214         }
4215         for (step = 0; step < steps; step++) {
4216             array[step] = data[step * src->ds_cnt];
4217         }
4218         qsort(array, step, sizeof(double), vdef_percent_compar);
4219
4220         field = (steps - 1) * dst->vf.param / 100;
4221         dst->vf.val = array[field];
4222         dst->vf.when = 0;   /* no time component */
4223         free(array);
4224 #if 0
4225         for (step = 0; step < steps; step++)
4226             printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4227                    step == field ? '*' : ' ');
4228 #endif
4229     }
4230         break;
4231     case VDEF_MAXIMUM:
4232         step = 0;
4233         while (step != steps && isnan(data[step * src->ds_cnt]))
4234             step++;
4235         if (step == steps) {
4236             dst->vf.val = DNAN;
4237             dst->vf.when = 0;
4238         } else {
4239             dst->vf.val = data[step * src->ds_cnt];
4240             dst->vf.when = src->start + (step + 1) * src->step;
4241         }
4242         while (step != steps) {
4243             if (finite(data[step * src->ds_cnt])) {
4244                 if (data[step * src->ds_cnt] > dst->vf.val) {
4245                     dst->vf.val = data[step * src->ds_cnt];
4246                     dst->vf.when = src->start + (step + 1) * src->step;
4247                 }
4248             }
4249             step++;
4250         }
4251         break;
4252     case VDEF_TOTAL:
4253     case VDEF_AVERAGE:{
4254         int       cnt = 0;
4255         double    sum = 0.0;
4256
4257         for (step = 0; step < steps; step++) {
4258             if (finite(data[step * src->ds_cnt])) {
4259                 sum += data[step * src->ds_cnt];
4260                 cnt++;
4261             };
4262         }
4263         if (cnt) {
4264             if (dst->vf.op == VDEF_TOTAL) {
4265                 dst->vf.val = sum * src->step;
4266                 dst->vf.when = 0;   /* no time component */
4267             } else {
4268                 dst->vf.val = sum / cnt;
4269                 dst->vf.when = 0;   /* no time component */
4270             };
4271         } else {
4272             dst->vf.val = DNAN;
4273             dst->vf.when = 0;
4274         }
4275     }
4276         break;
4277     case VDEF_MINIMUM:
4278         step = 0;
4279         while (step != steps && isnan(data[step * src->ds_cnt]))
4280             step++;
4281         if (step == steps) {
4282             dst->vf.val = DNAN;
4283             dst->vf.when = 0;
4284         } else {
4285             dst->vf.val = data[step * src->ds_cnt];
4286             dst->vf.when = src->start + (step + 1) * src->step;
4287         }
4288         while (step != steps) {
4289             if (finite(data[step * src->ds_cnt])) {
4290                 if (data[step * src->ds_cnt] < dst->vf.val) {
4291                     dst->vf.val = data[step * src->ds_cnt];
4292                     dst->vf.when = src->start + (step + 1) * src->step;
4293                 }
4294             }
4295             step++;
4296         }
4297         break;
4298     case VDEF_FIRST:
4299         /* The time value returned here is one step before the
4300          * actual time value.  This is the start of the first
4301          * non-NaN interval.
4302          */
4303         step = 0;
4304         while (step != steps && isnan(data[step * src->ds_cnt]))
4305             step++;
4306         if (step == steps) {    /* all entries were NaN */
4307             dst->vf.val = DNAN;
4308             dst->vf.when = 0;
4309         } else {
4310             dst->vf.val = data[step * src->ds_cnt];
4311             dst->vf.when = src->start + step * src->step;
4312         }
4313         break;
4314     case VDEF_LAST:
4315         /* The time value returned here is the
4316          * actual time value.  This is the end of the last
4317          * non-NaN interval.
4318          */
4319         step = steps - 1;
4320         while (step >= 0 && isnan(data[step * src->ds_cnt]))
4321             step--;
4322         if (step < 0) { /* all entries were NaN */
4323             dst->vf.val = DNAN;
4324             dst->vf.when = 0;
4325         } else {
4326             dst->vf.val = data[step * src->ds_cnt];
4327             dst->vf.when = src->start + (step + 1) * src->step;
4328         }
4329         break;
4330     case VDEF_LSLSLOPE:
4331     case VDEF_LSLINT:
4332     case VDEF_LSLCORREL:{
4333         /* Bestfit line by linear least squares method */
4334
4335         int       cnt = 0;
4336         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4337
4338         SUMx = 0;
4339         SUMy = 0;
4340         SUMxy = 0;
4341         SUMxx = 0;
4342         SUMyy = 0;
4343
4344         for (step = 0; step < steps; step++) {
4345             if (finite(data[step * src->ds_cnt])) {
4346                 cnt++;
4347                 SUMx += step;
4348                 SUMxx += step * step;
4349                 SUMxy += step * data[step * src->ds_cnt];
4350                 SUMy += data[step * src->ds_cnt];
4351                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4352             };
4353         }
4354
4355         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4356         y_intercept = (SUMy - slope * SUMx) / cnt;
4357         correl =
4358             (SUMxy -
4359              (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4360                                           (SUMx * SUMx) / cnt) * (SUMyy -
4361                                                                   (SUMy *
4362                                                                    SUMy) /
4363                                                                   cnt));
4364
4365         if (cnt) {
4366             if (dst->vf.op == VDEF_LSLSLOPE) {
4367                 dst->vf.val = slope;
4368                 dst->vf.when = 0;
4369             } else if (dst->vf.op == VDEF_LSLINT) {
4370                 dst->vf.val = y_intercept;
4371                 dst->vf.when = 0;
4372             } else if (dst->vf.op == VDEF_LSLCORREL) {
4373                 dst->vf.val = correl;
4374                 dst->vf.when = 0;
4375             };
4376
4377         } else {
4378             dst->vf.val = DNAN;
4379             dst->vf.when = 0;
4380         }
4381     }
4382         break;
4383     }
4384     return 0;
4385 }
4386
4387 /* NaN < -INF < finite_values < INF */
4388 int vdef_percent_compar(
4389     a,
4390     b)
4391     const void *a, *b;
4392 {
4393     /* Equality is not returned; this doesn't hurt except
4394      * (maybe) for a little performance.
4395      */
4396
4397     /* First catch NaN values. They are smallest */
4398     if (isnan(*(double *) a))
4399         return -1;
4400     if (isnan(*(double *) b))
4401         return 1;
4402
4403     /* NaN doesn't reach this part so INF and -INF are extremes.
4404      * The sign from isinf() is compatible with the sign we return
4405      */
4406     if (isinf(*(double *) a))
4407         return isinf(*(double *) a);
4408     if (isinf(*(double *) b))
4409         return isinf(*(double *) b);
4410
4411     /* If we reach this, both values must be finite */
4412     if (*(double *) a < *(double *) b)
4413         return -1;
4414     else
4415         return 1;
4416 }