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