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