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