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