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