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