make sure we do not try to draw points all that far outside the drawing area
[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     do {
1474         switch (baseint) {
1475         case TMT_SECOND:
1476             tm.       tm_sec += basestep;
1477
1478             break;
1479         case TMT_MINUTE:
1480             tm.       tm_min += basestep;
1481
1482             break;
1483         case TMT_HOUR:
1484             tm.       tm_hour += basestep;
1485
1486             break;
1487         case TMT_DAY:
1488             tm.       tm_mday += basestep;
1489
1490             break;
1491         case TMT_WEEK:
1492             tm.       tm_mday += 7 * basestep;
1493
1494             break;
1495         case TMT_MONTH:
1496             tm.       tm_mon += basestep;
1497
1498             break;
1499         case TMT_YEAR:
1500             tm.       tm_year += basestep;
1501         }
1502         madetime = mktime(&tm);
1503     } while (madetime == -1);   /* this is necessary to skip impssible times
1504                                    like the daylight saving time skips */
1505     return madetime;
1506
1507 }
1508
1509
1510 /* calculate values required for PRINT and GPRINT functions */
1511
1512 int print_calc(
1513     image_desc_t *im)
1514 {
1515     long      i, ii, validsteps;
1516     double    printval;
1517     struct tm tmvdef;
1518     int       graphelement = 0;
1519     long      vidx;
1520     int       max_ii;
1521     double    magfact = -1;
1522     char     *si_symb = "";
1523     char     *percent_s;
1524     int       prline_cnt = 0;
1525
1526     /* wow initializing tmvdef is quite a task :-) */
1527     time_t    now = time(NULL);
1528
1529     localtime_r(&now, &tmvdef);
1530     for (i = 0; i < im->gdes_c; i++) {
1531         vidx = im->gdes[i].vidx;
1532         switch (im->gdes[i].gf) {
1533         case GF_PRINT:
1534         case GF_GPRINT:
1535             /* PRINT and GPRINT can now print VDEF generated values.
1536              * There's no need to do any calculations on them as these
1537              * calculations were already made.
1538              */
1539             if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1540                 printval = im->gdes[vidx].vf.val;
1541                 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1542             } else {    /* need to calculate max,min,avg etcetera */
1543                 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1544                           / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1545                 printval = DNAN;
1546                 validsteps = 0;
1547                 for (ii = im->gdes[vidx].ds;
1548                      ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1549                     if (!finite(im->gdes[vidx].data[ii]))
1550                         continue;
1551                     if (isnan(printval)) {
1552                         printval = im->gdes[vidx].data[ii];
1553                         validsteps++;
1554                         continue;
1555                     }
1556
1557                     switch (im->gdes[i].cf) {
1558                     case CF_HWPREDICT:
1559                     case CF_MHWPREDICT:
1560                     case CF_DEVPREDICT:
1561                     case CF_DEVSEASONAL:
1562                     case CF_SEASONAL:
1563                     case CF_AVERAGE:
1564                         validsteps++;
1565                         printval += im->gdes[vidx].data[ii];
1566                         break;
1567                     case CF_MINIMUM:
1568                         printval = min(printval, im->gdes[vidx].data[ii]);
1569                         break;
1570                     case CF_FAILURES:
1571                     case CF_MAXIMUM:
1572                         printval = max(printval, im->gdes[vidx].data[ii]);
1573                         break;
1574                     case CF_LAST:
1575                         printval = im->gdes[vidx].data[ii];
1576                     }
1577                 }
1578                 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1579                     if (validsteps > 1) {
1580                         printval = (printval / validsteps);
1581                     }
1582                 }
1583             }           /* prepare printval */
1584
1585             if (!im->gdes[i].strftm && (percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1586                 /* Magfact is set to -1 upon entry to print_calc.  If it
1587                  * is still less than 0, then we need to run auto_scale.
1588                  * Otherwise, put the value into the correct units.  If
1589                  * the value is 0, then do not set the symbol or magnification
1590                  * so next the calculation will be performed again. */
1591                 if (magfact < 0.0) {
1592                     auto_scale(im, &printval, &si_symb, &magfact);
1593                     if (printval == 0.0)
1594                         magfact = -1.0;
1595                 } else {
1596                     printval /= magfact;
1597                 }
1598                 *(++percent_s) = 's';
1599             } else if (!im->gdes[i].strftm && strstr(im->gdes[i].format, "%s") != NULL) {
1600                 auto_scale(im, &printval, &si_symb, &magfact);
1601             }
1602
1603             if (im->gdes[i].gf == GF_PRINT) {
1604                 rrd_infoval_t prline;
1605
1606                 if (im->gdes[i].strftm) {
1607                     prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1608                     if (im->gdes[vidx].vf.never == 1) {
1609                        time_clean(prline.u_str, im->gdes[i].format);
1610                     } else {
1611                         strftime(prline.u_str,
1612                                  FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1613                     }
1614                 } else if (bad_format(im->gdes[i].format)) {
1615                     rrd_set_error
1616                         ("bad format for PRINT in '%s'", im->gdes[i].format);
1617                     return -1;
1618                 } else {
1619                     prline.u_str =
1620                         sprintf_alloc(im->gdes[i].format, printval, si_symb);
1621                 }
1622                 grinfo_push(im,
1623                             sprintf_alloc
1624                             ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1625                 free(prline.u_str);
1626             } else {
1627                 /* GF_GPRINT */
1628
1629                 if (im->gdes[i].strftm) {
1630                     if (im->gdes[vidx].vf.never == 1) {
1631                        time_clean(im->gdes[i].legend, im->gdes[i].format);
1632                     } else {
1633                         strftime(im->gdes[i].legend,
1634                                  FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1635                     }
1636                 } else {
1637                     if (bad_format(im->gdes[i].format)) {
1638                         rrd_set_error
1639                             ("bad format for GPRINT in '%s'",
1640                              im->gdes[i].format);
1641                         return -1;
1642                     }
1643 #ifdef HAVE_SNPRINTF
1644                     snprintf(im->gdes[i].legend,
1645                              FMT_LEG_LEN - 2,
1646                              im->gdes[i].format, printval, si_symb);
1647 #else
1648                     sprintf(im->gdes[i].legend,
1649                             im->gdes[i].format, printval, si_symb);
1650 #endif
1651                 }
1652                 graphelement = 1;
1653             }
1654             break;
1655         case GF_LINE:
1656         case GF_AREA:
1657                 case GF_GRAD:
1658         case GF_TICK:
1659             graphelement = 1;
1660             break;
1661         case GF_HRULE:
1662             if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1663                 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1664             };
1665             graphelement = 1;
1666             break;
1667         case GF_VRULE:
1668             if (im->gdes[i].xrule == 0) {   /* again ... the legend printer needs it */
1669                 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1670             };
1671             graphelement = 1;
1672             break;
1673         case GF_COMMENT:
1674         case GF_TEXTALIGN:
1675         case GF_DEF:
1676         case GF_CDEF:
1677         case GF_VDEF:
1678 #ifdef WITH_PIECHART
1679         case GF_PART:
1680 #endif
1681         case GF_SHIFT:
1682         case GF_XPORT:
1683             break;
1684         case GF_STACK:
1685             rrd_set_error
1686                 ("STACK should already be turned into LINE or AREA here");
1687             return -1;
1688             break;
1689         }
1690     }
1691     return graphelement;
1692 }
1693
1694
1695
1696 /* place legends with color spots */
1697 int leg_place(
1698     image_desc_t *im,
1699     int calc_width)
1700 {
1701     /* graph labels */
1702     int       interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1703     int       border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1704     int       fill = 0, fill_last;
1705     double    legendwidth; // = im->ximg - 2 * border;
1706     int       leg_c = 0;
1707     double    leg_x = border;
1708     int       leg_y = 0; //im->yimg;
1709     int       leg_y_prev = 0; // im->yimg;
1710     int       leg_cc;
1711     double    glue = 0;
1712     int       i, ii, mark = 0;
1713     char      default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1714     int      *legspace;
1715     char     *tab;
1716     char      saved_legend[FMT_LEG_LEN + 5];
1717
1718     if(calc_width){
1719         legendwidth = 0;
1720     }
1721     else{
1722         legendwidth = im->legendwidth - 2 * border;
1723     }
1724
1725
1726     if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1727         if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1728             rrd_set_error("malloc for legspace");
1729             return -1;
1730         }
1731
1732         for (i = 0; i < im->gdes_c; i++) {
1733             char      prt_fctn; /*special printfunctions */
1734             if(calc_width){
1735                 strcpy(saved_legend, im->gdes[i].legend);
1736             }
1737
1738             fill_last = fill;
1739             /* hide legends for rules which are not displayed */
1740             if (im->gdes[i].gf == GF_TEXTALIGN) {
1741                 default_txtalign = im->gdes[i].txtalign;
1742             }
1743
1744             if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1745                 if (im->gdes[i].gf == GF_HRULE
1746                     && (im->gdes[i].yrule <
1747                         im->minval || im->gdes[i].yrule > im->maxval))
1748                     im->gdes[i].legend[0] = '\0';
1749                 if (im->gdes[i].gf == GF_VRULE
1750                     && (im->gdes[i].xrule <
1751                         im->start || im->gdes[i].xrule > im->end))
1752                     im->gdes[i].legend[0] = '\0';
1753             }
1754
1755             /* turn \\t into tab */
1756             while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1757                 memmove(tab, tab + 1, strlen(tab));
1758                 tab[0] = (char) 9;
1759             }
1760
1761             leg_cc = strlen(im->gdes[i].legend);
1762             /* is there a controle code at the end of the legend string ? */
1763             if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1764                 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1765                 leg_cc -= 2;
1766                 im->gdes[i].legend[leg_cc] = '\0';
1767             } else {
1768                 prt_fctn = '\0';
1769             }
1770             /* only valid control codes */
1771             if (prt_fctn != 'l' && prt_fctn != 'n' &&   /* a synonym for l */
1772                 prt_fctn != 'r' &&
1773                 prt_fctn != 'j' &&
1774                 prt_fctn != 'c' &&
1775                 prt_fctn != 'u' &&
1776                 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1777                 free(legspace);
1778                 rrd_set_error
1779                     ("Unknown control code at the end of '%s\\%c'",
1780                      im->gdes[i].legend, prt_fctn);
1781                 return -1;
1782             }
1783             /* \n -> \l */
1784             if (prt_fctn == 'n') {
1785                 prt_fctn = 'l';
1786             }
1787
1788             /* remove exess space from the end of the legend for \g */
1789             while (prt_fctn == 'g' &&
1790                    leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1791                 leg_cc--;
1792                 im->gdes[i].legend[leg_cc] = '\0';
1793             }
1794
1795             if (leg_cc != 0) {
1796
1797                 /* no interleg space if string ends in \g */
1798                 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1799                 if (fill > 0) {
1800                     fill += legspace[i];
1801                 }
1802                 fill +=
1803                     gfx_get_text_width(im,
1804                                        fill + border,
1805                                        im->
1806                                        text_prop
1807                                        [TEXT_PROP_LEGEND].
1808                                        font_desc,
1809                                        im->tabwidth, im->gdes[i].legend);
1810                 leg_c++;
1811             } else {
1812                 legspace[i] = 0;
1813             }
1814             /* who said there was a special tag ... ? */
1815             if (prt_fctn == 'g') {
1816                 prt_fctn = '\0';
1817             }
1818
1819             if (prt_fctn == '\0') {
1820                 if(calc_width && (fill > legendwidth)){
1821                     legendwidth = fill;
1822                 }
1823                 if (i == im->gdes_c - 1 || fill > legendwidth) {
1824                     /* just one legend item is left right or center */
1825                     switch (default_txtalign) {
1826                     case TXA_RIGHT:
1827                         prt_fctn = 'r';
1828                         break;
1829                     case TXA_CENTER:
1830                         prt_fctn = 'c';
1831                         break;
1832                     case TXA_JUSTIFIED:
1833                         prt_fctn = 'j';
1834                         break;
1835                     default:
1836                         prt_fctn = 'l';
1837                         break;
1838                     }
1839                 }
1840                 /* is it time to place the legends ? */
1841                 if (fill > legendwidth) {
1842                     if (leg_c > 1) {
1843                         /* go back one */
1844                         i--;
1845                         fill = fill_last;
1846                         leg_c--;
1847                     }
1848                 }
1849                 if (leg_c == 1 && prt_fctn == 'j') {
1850                     prt_fctn = 'l';
1851                 }
1852             }
1853
1854             if (prt_fctn != '\0') {
1855                 leg_x = border;
1856                 if (leg_c >= 2 && prt_fctn == 'j') {
1857                     glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1858                 } else {
1859                     glue = 0;
1860                 }
1861                 if (prt_fctn == 'c')
1862                     leg_x = (double)(legendwidth - fill) / 2.0;
1863                 if (prt_fctn == 'r')
1864                     leg_x = legendwidth - fill + border;
1865                 for (ii = mark; ii <= i; ii++) {
1866                     if (im->gdes[ii].legend[0] == '\0')
1867                         continue;   /* skip empty legends */
1868                     im->gdes[ii].leg_x = leg_x;
1869                     im->gdes[ii].leg_y = leg_y + border;
1870                     leg_x +=
1871                         (double)gfx_get_text_width(im, leg_x,
1872                                            im->
1873                                            text_prop
1874                                            [TEXT_PROP_LEGEND].
1875                                            font_desc,
1876                                            im->tabwidth, im->gdes[ii].legend)
1877                         +(double)legspace[ii]
1878                         + glue;
1879                 }
1880                 leg_y_prev = leg_y;
1881                 if (leg_x > border || prt_fctn == 's')
1882                     leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1883                 if (prt_fctn == 's')
1884                     leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1885                 if (prt_fctn == 'u')
1886                     leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1887
1888                 if(calc_width && (fill > legendwidth)){
1889                     legendwidth = fill;
1890                 }
1891                 fill = 0;
1892                 leg_c = 0;
1893                 mark = ii;
1894             }
1895
1896             if(calc_width){
1897                 strcpy(im->gdes[i].legend, saved_legend);
1898             }
1899         }
1900
1901         if(calc_width){
1902             im->legendwidth = legendwidth + 2 * border;
1903         }
1904         else{
1905             im->legendheight = leg_y + border * 0.6;
1906         }
1907         free(legspace);
1908     }
1909     return 0;
1910 }
1911
1912 /* create a grid on the graph. it determines what to do
1913    from the values of xsize, start and end */
1914
1915 /* the xaxis labels are determined from the number of seconds per pixel
1916    in the requested graph */
1917
1918 int calc_horizontal_grid(
1919     image_desc_t
1920     *im)
1921 {
1922     double    range;
1923     double    scaledrange;
1924     int       pixel, i;
1925     int       gridind = 0;
1926     int       decimals, fractionals;
1927
1928     im->ygrid_scale.labfact = 2;
1929     range = im->maxval - im->minval;
1930     scaledrange = range / im->magfact;
1931     /* does the scale of this graph make it impossible to put lines
1932        on it? If so, give up. */
1933     if (isnan(scaledrange)) {
1934         return 0;
1935     }
1936
1937     /* find grid spaceing */
1938     pixel = 1;
1939     if (isnan(im->ygridstep)) {
1940         if (im->extra_flags & ALTYGRID) {
1941             /* find the value with max number of digits. Get number of digits */
1942             decimals =
1943                 ceil(log10
1944                      (max(fabs(im->maxval), fabs(im->minval)) *
1945                       im->viewfactor / im->magfact));
1946             if (decimals <= 0)  /* everything is small. make place for zero */
1947                 decimals = 1;
1948             im->ygrid_scale.gridstep =
1949                 pow((double) 10,
1950                     floor(log10(range * im->viewfactor / im->magfact))) /
1951                 im->viewfactor * im->magfact;
1952             if (im->ygrid_scale.gridstep == 0)  /* range is one -> 0.1 is reasonable scale */
1953                 im->ygrid_scale.gridstep = 0.1;
1954             /* should have at least 5 lines but no more then 15 */
1955             if (range / im->ygrid_scale.gridstep < 5
1956                 && im->ygrid_scale.gridstep >= 30)
1957                 im->ygrid_scale.gridstep /= 10;
1958             if (range / im->ygrid_scale.gridstep > 15)
1959                 im->ygrid_scale.gridstep *= 10;
1960             if (range / im->ygrid_scale.gridstep > 5) {
1961                 im->ygrid_scale.labfact = 1;
1962                 if (range / im->ygrid_scale.gridstep > 8
1963                     || im->ygrid_scale.gridstep <
1964                     1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1965                     im->ygrid_scale.labfact = 2;
1966             } else {
1967                 im->ygrid_scale.gridstep /= 5;
1968                 im->ygrid_scale.labfact = 5;
1969             }
1970             fractionals =
1971                 floor(log10
1972                       (im->ygrid_scale.gridstep *
1973                        (double) im->ygrid_scale.labfact * im->viewfactor /
1974                        im->magfact));
1975             if (fractionals < 0) {  /* small amplitude. */
1976                 int       len = decimals - fractionals + 1;
1977
1978                 if (im->unitslength < len + 2)
1979                     im->unitslength = len + 2;
1980                 sprintf(im->ygrid_scale.labfmt,
1981                         "%%%d.%df%s", len,
1982                         -fractionals, (im->symbol != ' ' ? " %c" : ""));
1983             } else {
1984                 int       len = decimals + 1;
1985
1986                 if (im->unitslength < len + 2)
1987                     im->unitslength = len + 2;
1988                 sprintf(im->ygrid_scale.labfmt,
1989                         "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1990             }
1991         } else {        /* classic rrd grid */
1992             for (i = 0; ylab[i].grid > 0; i++) {
1993                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1994                 gridind = i;
1995                 if (pixel >= 5)
1996                     break;
1997             }
1998
1999             for (i = 0; i < 4; i++) {
2000                 if (pixel * ylab[gridind].lfac[i] >=
2001                     1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
2002                     im->ygrid_scale.labfact = ylab[gridind].lfac[i];
2003                     break;
2004                 }
2005             }
2006
2007             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
2008         }
2009     } else {
2010         im->ygrid_scale.gridstep = im->ygridstep;
2011         im->ygrid_scale.labfact = im->ylabfact;
2012     }
2013     return 1;
2014 }
2015
2016 int draw_horizontal_grid(
2017     image_desc_t
2018     *im)
2019 {
2020     int       i;
2021     double    scaledstep;
2022     char      graph_label[100];
2023     int       nlabels = 0;
2024     double    X0 = im->xorigin;
2025     double    X1 = im->xorigin + im->xsize;
2026     int       sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2027     int       egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2028     double    MaxY;
2029     double second_axis_magfact = 0;
2030     char *second_axis_symb = "";
2031
2032     scaledstep =
2033         im->ygrid_scale.gridstep /
2034         (double) im->magfact * (double) im->viewfactor;
2035     MaxY = scaledstep * (double) egrid;
2036     for (i = sgrid; i <= egrid; i++) {
2037         double    Y0 = ytr(im,
2038                            im->ygrid_scale.gridstep * i);
2039         double    YN = ytr(im,
2040                            im->ygrid_scale.gridstep * (i + 1));
2041
2042         if (floor(Y0 + 0.5) >=
2043             im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2044             /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2045                with the chosen settings. Add a label if required by settings, or if
2046                there is only one label so far and the next grid line is out of bounds. */
2047             if (i % im->ygrid_scale.labfact == 0
2048                 || (nlabels == 1
2049                     && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2050                 if (im->symbol == ' ') {
2051                     if (im->extra_flags & ALTYGRID) {
2052                         sprintf(graph_label,
2053                                 im->ygrid_scale.labfmt,
2054                                 scaledstep * (double) i);
2055                     } else {
2056                         if (MaxY < 10) {
2057                             sprintf(graph_label, "%4.1f",
2058                                     scaledstep * (double) i);
2059                         } else {
2060                             sprintf(graph_label, "%4.0f",
2061                                     scaledstep * (double) i);
2062                         }
2063                     }
2064                 } else {
2065                     char      sisym = (i == 0 ? ' ' : im->symbol);
2066
2067                     if (im->extra_flags & ALTYGRID) {
2068                         sprintf(graph_label,
2069                                 im->ygrid_scale.labfmt,
2070                                 scaledstep * (double) i, sisym);
2071                     } else {
2072                         if (MaxY < 10) {
2073                             sprintf(graph_label, "%4.1f %c",
2074                                     scaledstep * (double) i, sisym);
2075                         } else {
2076                             sprintf(graph_label, "%4.0f %c",
2077                                     scaledstep * (double) i, sisym);
2078                         }
2079                     }
2080                 }
2081                 nlabels++;
2082                 if (im->second_axis_scale != 0){
2083                         char graph_label_right[100];
2084                         double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2085                         if (im->second_axis_format[0] == '\0'){
2086                             if (!second_axis_magfact){
2087                                 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2088                                 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2089                             }
2090                             sval /= second_axis_magfact;
2091
2092                             if(MaxY < 10) {
2093                                 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2094                             } else {
2095                                 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2096                             }
2097                         }
2098                         else {
2099                            sprintf(graph_label_right,im->second_axis_format,sval);
2100                         }
2101                         gfx_text ( im,
2102                                X1+7, Y0,
2103                                im->graph_col[GRC_FONT],
2104                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2105                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2106                                graph_label_right );
2107                 }
2108
2109                 gfx_text(im,
2110                          X0 -
2111                          im->
2112                          text_prop[TEXT_PROP_AXIS].
2113                          size, Y0,
2114                          im->graph_col[GRC_FONT],
2115                          im->
2116                          text_prop[TEXT_PROP_AXIS].
2117                          font_desc,
2118                          im->tabwidth, 0.0,
2119                          GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2120                 gfx_line(im, X0 - 2, Y0, X0, Y0,
2121                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2122                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2123                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2124                 gfx_dashed_line(im, X0 - 2, Y0,
2125                                 X1 + 2, Y0,
2126                                 MGRIDWIDTH,
2127                                 im->
2128                                 graph_col
2129                                 [GRC_MGRID],
2130                                 im->grid_dash_on, im->grid_dash_off);
2131             } else if (!(im->extra_flags & NOMINOR)) {
2132                 gfx_line(im,
2133                          X0 - 2, Y0,
2134                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2135                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2136                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2137                 gfx_dashed_line(im, X0 - 1, Y0,
2138                                 X1 + 1, Y0,
2139                                 GRIDWIDTH,
2140                                 im->
2141                                 graph_col[GRC_GRID],
2142                                 im->grid_dash_on, im->grid_dash_off);
2143             }
2144         }
2145     }
2146     return 1;
2147 }
2148
2149 /* this is frexp for base 10 */
2150 double    frexp10(
2151     double,
2152     double *);
2153 double frexp10(
2154     double x,
2155     double *e)
2156 {
2157     double    mnt;
2158     int       iexp;
2159
2160     iexp = floor(log((double)fabs(x)) / log((double)10));
2161     mnt = x / pow(10.0, iexp);
2162     if (mnt >= 10.0) {
2163         iexp++;
2164         mnt = x / pow(10.0, iexp);
2165     }
2166     *e = iexp;
2167     return mnt;
2168 }
2169
2170
2171 /* logaritmic horizontal grid */
2172 int horizontal_log_grid(
2173     image_desc_t
2174     *im)
2175 {
2176     double    yloglab[][10] = {
2177         {
2178          1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2179          0.0, 0.0, 0.0}, {
2180                           1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2181                           0.0, 0.0, 0.0}, {
2182                                            1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2183                                            0.0, 0.0, 0.0}, {
2184                                                             1.0, 2.0, 4.0,
2185                                                             6.0, 8.0, 10.,
2186                                                             0.0,
2187                                                             0.0, 0.0, 0.0}, {
2188                                                                              1.0,
2189                                                                              2.0,
2190                                                                              3.0,
2191                                                                              4.0,
2192                                                                              5.0,
2193                                                                              6.0,
2194                                                                              7.0,
2195                                                                              8.0,
2196                                                                              9.0,
2197                                                                              10.},
2198         {
2199          0, 0, 0, 0, 0, 0, 0, 0, 0, 0}  /* last line */
2200     };
2201     int       i, j, val_exp, min_exp;
2202     double    nex;      /* number of decades in data */
2203     double    logscale; /* scale in logarithmic space */
2204     int       exfrac = 1;   /* decade spacing */
2205     int       mid = -1; /* row in yloglab for major grid */
2206     double    mspac;    /* smallest major grid spacing (pixels) */
2207     int       flab;     /* first value in yloglab to use */
2208     double    value, tmp, pre_value;
2209     double    X0, X1, Y0;
2210     char      graph_label[100];
2211
2212     nex = log10(im->maxval / im->minval);
2213     logscale = im->ysize / nex;
2214     /* major spacing for data with high dynamic range */
2215     while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2216         if (exfrac == 1)
2217             exfrac = 3;
2218         else
2219             exfrac += 3;
2220     }
2221
2222     /* major spacing for less dynamic data */
2223     do {
2224         /* search best row in yloglab */
2225         mid++;
2226         for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2227         mspac = logscale * log10(10.0 / yloglab[mid][i]);
2228     }
2229     while (mspac >
2230            2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2231     if (mid)
2232         mid--;
2233     /* find first value in yloglab */
2234     for (flab = 0;
2235          yloglab[mid][flab] < 10
2236          && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2237     if (yloglab[mid][flab] == 10.0) {
2238         tmp += 1.0;
2239         flab = 0;
2240     }
2241     val_exp = tmp;
2242     if (val_exp % exfrac)
2243         val_exp += abs(-val_exp % exfrac);
2244     X0 = im->xorigin;
2245     X1 = im->xorigin + im->xsize;
2246     /* draw grid */
2247     pre_value = DNAN;
2248     while (1) {
2249
2250         value = yloglab[mid][flab] * pow(10.0, val_exp);
2251         if (AlmostEqual2sComplement(value, pre_value, 4))
2252             break;      /* it seems we are not converging */
2253         pre_value = value;
2254         Y0 = ytr(im, value);
2255         if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2256             break;
2257         /* major grid line */
2258         gfx_line(im,
2259                  X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2260         gfx_line(im, X1, Y0, X1 + 2, Y0,
2261                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2262         gfx_dashed_line(im, X0 - 2, Y0,
2263                         X1 + 2, Y0,
2264                         MGRIDWIDTH,
2265                         im->
2266                         graph_col
2267                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2268         /* label */
2269         if (im->extra_flags & FORCE_UNITS_SI) {
2270             int       scale;
2271             double    pvalue;
2272             char      symbol;
2273
2274             scale = floor(val_exp / 3.0);
2275             if (value >= 1.0)
2276                 pvalue = pow(10.0, val_exp % 3);
2277             else
2278                 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2279             pvalue *= yloglab[mid][flab];
2280             if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2281                 && ((scale + si_symbcenter) >= 0))
2282                 symbol = si_symbol[scale + si_symbcenter];
2283             else
2284                 symbol = '?';
2285             sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2286         } else {
2287             sprintf(graph_label, "%3.0e", value);
2288         }
2289         if (im->second_axis_scale != 0){
2290                 char graph_label_right[100];
2291                 double sval = value*im->second_axis_scale+im->second_axis_shift;
2292                 if (im->second_axis_format[0] == '\0'){
2293                         if (im->extra_flags & FORCE_UNITS_SI) {
2294                                 double mfac = 1;
2295                                 char   *symb = "";
2296                                 auto_scale(im,&sval,&symb,&mfac);
2297                                 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2298                         }
2299                         else {
2300                                 sprintf(graph_label_right,"%3.0e", sval);
2301                         }
2302                 }
2303                 else {
2304                       sprintf(graph_label_right,im->second_axis_format,sval,"");
2305                 }
2306
2307                 gfx_text ( im,
2308                                X1+7, Y0,
2309                                im->graph_col[GRC_FONT],
2310                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2311                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2312                                graph_label_right );
2313         }
2314
2315         gfx_text(im,
2316                  X0 -
2317                  im->
2318                  text_prop[TEXT_PROP_AXIS].
2319                  size, Y0,
2320                  im->graph_col[GRC_FONT],
2321                  im->
2322                  text_prop[TEXT_PROP_AXIS].
2323                  font_desc,
2324                  im->tabwidth, 0.0,
2325                  GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2326         /* minor grid */
2327         if (mid < 4 && exfrac == 1) {
2328             /* find first and last minor line behind current major line
2329              * i is the first line and j tha last */
2330             if (flab == 0) {
2331                 min_exp = val_exp - 1;
2332                 for (i = 1; yloglab[mid][i] < 10.0; i++);
2333                 i = yloglab[mid][i - 1] + 1;
2334                 j = 10;
2335             } else {
2336                 min_exp = val_exp;
2337                 i = yloglab[mid][flab - 1] + 1;
2338                 j = yloglab[mid][flab];
2339             }
2340
2341             /* draw minor lines below current major line */
2342             for (; i < j; i++) {
2343
2344                 value = i * pow(10.0, min_exp);
2345                 if (value < im->minval)
2346                     continue;
2347                 Y0 = ytr(im, value);
2348                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2349                     break;
2350                 /* draw lines */
2351                 gfx_line(im,
2352                          X0 - 2, Y0,
2353                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2354                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2355                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2356                 gfx_dashed_line(im, X0 - 1, Y0,
2357                                 X1 + 1, Y0,
2358                                 GRIDWIDTH,
2359                                 im->
2360                                 graph_col[GRC_GRID],
2361                                 im->grid_dash_on, im->grid_dash_off);
2362             }
2363         } else if (exfrac > 1) {
2364             for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2365                 value = pow(10.0, i);
2366                 if (value < im->minval)
2367                     continue;
2368                 Y0 = ytr(im, value);
2369                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2370                     break;
2371                 /* draw lines */
2372                 gfx_line(im,
2373                          X0 - 2, Y0,
2374                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2375                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2376                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2377                 gfx_dashed_line(im, X0 - 1, Y0,
2378                                 X1 + 1, Y0,
2379                                 GRIDWIDTH,
2380                                 im->
2381                                 graph_col[GRC_GRID],
2382                                 im->grid_dash_on, im->grid_dash_off);
2383             }
2384         }
2385
2386         /* next decade */
2387         if (yloglab[mid][++flab] == 10.0) {
2388             flab = 0;
2389             val_exp += exfrac;
2390         }
2391     }
2392
2393     /* draw minor lines after highest major line */
2394     if (mid < 4 && exfrac == 1) {
2395         /* find first and last minor line below current major line
2396          * i is the first line and j tha last */
2397         if (flab == 0) {
2398             min_exp = val_exp - 1;
2399             for (i = 1; yloglab[mid][i] < 10.0; i++);
2400             i = yloglab[mid][i - 1] + 1;
2401             j = 10;
2402         } else {
2403             min_exp = val_exp;
2404             i = yloglab[mid][flab - 1] + 1;
2405             j = yloglab[mid][flab];
2406         }
2407
2408         /* draw minor lines below current major line */
2409         for (; i < j; i++) {
2410
2411             value = i * pow(10.0, min_exp);
2412             if (value < im->minval)
2413                 continue;
2414             Y0 = ytr(im, value);
2415             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2416                 break;
2417             /* draw lines */
2418             gfx_line(im,
2419                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2420             gfx_line(im, X1, Y0, X1 + 2, Y0,
2421                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2422             gfx_dashed_line(im, X0 - 1, Y0,
2423                             X1 + 1, Y0,
2424                             GRIDWIDTH,
2425                             im->
2426                             graph_col[GRC_GRID],
2427                             im->grid_dash_on, im->grid_dash_off);
2428         }
2429     }
2430     /* fancy minor gridlines */
2431     else if (exfrac > 1) {
2432         for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2433             value = pow(10.0, i);
2434             if (value < im->minval)
2435                 continue;
2436             Y0 = ytr(im, value);
2437             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2438                 break;
2439             /* draw lines */
2440             gfx_line(im,
2441                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2442             gfx_line(im, X1, Y0, X1 + 2, Y0,
2443                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2444             gfx_dashed_line(im, X0 - 1, Y0,
2445                             X1 + 1, Y0,
2446                             GRIDWIDTH,
2447                             im->
2448                             graph_col[GRC_GRID],
2449                             im->grid_dash_on, im->grid_dash_off);
2450         }
2451     }
2452
2453     return 1;
2454 }
2455
2456
2457 void vertical_grid(
2458     image_desc_t *im)
2459 {
2460     int       xlab_sel; /* which sort of label and grid ? */
2461     time_t    ti, tilab, timajor;
2462     long      factor;
2463     char      graph_label[100];
2464     double    X0, Y0, Y1;   /* points for filled graph and more */
2465     struct tm tm;
2466
2467     /* the type of time grid is determined by finding
2468        the number of seconds per pixel in the graph */
2469     if (im->xlab_user.minsec == -1) {
2470         factor = (im->end - im->start) / im->xsize;
2471         xlab_sel = 0;
2472         while (xlab[xlab_sel + 1].minsec !=
2473                -1 && xlab[xlab_sel + 1].minsec <= factor) {
2474             xlab_sel++;
2475         }               /* pick the last one */
2476         while (xlab[xlab_sel - 1].minsec ==
2477                xlab[xlab_sel].minsec
2478                && xlab[xlab_sel].length > (im->end - im->start)) {
2479             xlab_sel--;
2480         }               /* go back to the smallest size */
2481         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2482         im->xlab_user.gridst = xlab[xlab_sel].gridst;
2483         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2484         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2485         im->xlab_user.labtm = xlab[xlab_sel].labtm;
2486         im->xlab_user.labst = xlab[xlab_sel].labst;
2487         im->xlab_user.precis = xlab[xlab_sel].precis;
2488         im->xlab_user.stst = xlab[xlab_sel].stst;
2489     }
2490
2491     /* y coords are the same for every line ... */
2492     Y0 = im->yorigin;
2493     Y1 = im->yorigin - im->ysize;
2494     /* paint the minor grid */
2495     if (!(im->extra_flags & NOMINOR)) {
2496         for (ti = find_first_time(im->start,
2497                                   im->
2498                                   xlab_user.
2499                                   gridtm,
2500                                   im->
2501                                   xlab_user.
2502                                   gridst),
2503              timajor =
2504              find_first_time(im->start,
2505                              im->xlab_user.
2506                              mgridtm,
2507                              im->xlab_user.
2508                              mgridst);
2509              ti < im->end;
2510              ti =
2511              find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2512             ) {
2513             /* are we inside the graph ? */
2514             if (ti < im->start || ti > im->end)
2515                 continue;
2516             while (timajor < ti) {
2517                 timajor = find_next_time(timajor,
2518                                          im->
2519                                          xlab_user.
2520                                          mgridtm, im->xlab_user.mgridst);
2521             }
2522             if (ti == timajor)
2523                 continue;   /* skip as falls on major grid line */
2524             X0 = xtr(im, ti);
2525             gfx_line(im, X0, Y1 - 2, X0, Y1,
2526                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2527             gfx_line(im, X0, Y0, X0, Y0 + 2,
2528                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2529             gfx_dashed_line(im, X0, Y0 + 1, X0,
2530                             Y1 - 1, GRIDWIDTH,
2531                             im->
2532                             graph_col[GRC_GRID],
2533                             im->grid_dash_on, im->grid_dash_off);
2534         }
2535     }
2536
2537     /* paint the major grid */
2538     for (ti = find_first_time(im->start,
2539                               im->
2540                               xlab_user.
2541                               mgridtm,
2542                               im->
2543                               xlab_user.
2544                               mgridst);
2545          ti < im->end;
2546          ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2547         ) {
2548         /* are we inside the graph ? */
2549         if (ti < im->start || ti > im->end)
2550             continue;
2551         X0 = xtr(im, ti);
2552         gfx_line(im, X0, Y1 - 2, X0, Y1,
2553                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2554         gfx_line(im, X0, Y0, X0, Y0 + 3,
2555                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2556         gfx_dashed_line(im, X0, Y0 + 3, X0,
2557                         Y1 - 2, MGRIDWIDTH,
2558                         im->
2559                         graph_col
2560                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2561     }
2562     /* paint the labels below the graph */
2563     for (ti =
2564          find_first_time(im->start -
2565                          im->xlab_user.
2566                          precis / 2,
2567                          im->xlab_user.
2568                          labtm,
2569                          im->xlab_user.
2570                          labst);
2571          ti <=
2572          im->end -
2573          im->xlab_user.precis / 2;
2574          ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2575         ) {
2576         tilab = ti + im->xlab_user.precis / 2;  /* correct time for the label */
2577         /* are we inside the graph ? */
2578         if (tilab < im->start || tilab > im->end)
2579             continue;
2580 #if HAVE_STRFTIME
2581         localtime_r(&tilab, &tm);
2582         strftime(graph_label, 99, im->xlab_user.stst, &tm);
2583 #else
2584 # error "your libc has no strftime I guess we'll abort the exercise here."
2585 #endif
2586         gfx_text(im,
2587                  xtr(im, tilab),
2588                  Y0 + 3,
2589                  im->graph_col[GRC_FONT],
2590                  im->
2591                  text_prop[TEXT_PROP_AXIS].
2592                  font_desc,
2593                  im->tabwidth, 0.0,
2594                  GFX_H_CENTER, GFX_V_TOP, graph_label);
2595     }
2596
2597 }
2598
2599
2600 void axis_paint(
2601     image_desc_t *im)
2602 {
2603     /* draw x and y axis */
2604     /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2605        im->xorigin+im->xsize,im->yorigin-im->ysize,
2606        GRIDWIDTH, im->graph_col[GRC_AXIS]);
2607
2608        gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2609        im->xorigin+im->xsize,im->yorigin-im->ysize,
2610        GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2611
2612     gfx_line(im, im->xorigin - 4,
2613              im->yorigin,
2614              im->xorigin + im->xsize +
2615              4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2616     gfx_line(im, im->xorigin,
2617              im->yorigin + 4,
2618              im->xorigin,
2619              im->yorigin - im->ysize -
2620              4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2621     /* arrow for X and Y axis direction */
2622     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 */
2623                  im->graph_col[GRC_ARROW]);
2624     gfx_close_path(im);
2625     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 */
2626                  im->graph_col[GRC_ARROW]);
2627     gfx_close_path(im);
2628     if (im->second_axis_scale != 0){
2629        gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2630                          im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2631                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2632        gfx_new_area ( im,
2633                    im->xorigin+im->xsize-2,  im->yorigin-im->ysize-2,
2634                    im->xorigin+im->xsize+3,  im->yorigin-im->ysize-2,
2635                    im->xorigin+im->xsize,    im->yorigin-im->ysize-7, /* LINEOFFSET */
2636                    im->graph_col[GRC_ARROW]);
2637        gfx_close_path(im);
2638     }
2639
2640 }
2641
2642 void grid_paint(
2643     image_desc_t *im)
2644 {
2645     long      i;
2646     int       res = 0;
2647     double    X0, Y0;   /* points for filled graph and more */
2648     struct gfx_color_t water_color;
2649
2650     if (im->draw_3d_border > 0) {
2651             /* draw 3d border */
2652             i = im->draw_3d_border;
2653             gfx_new_area(im, 0, im->yimg,
2654                          i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2655             gfx_add_point(im, im->ximg - i, i);
2656             gfx_add_point(im, im->ximg, 0);
2657             gfx_add_point(im, 0, 0);
2658             gfx_close_path(im);
2659             gfx_new_area(im, i, im->yimg - i,
2660                          im->ximg - i,
2661                          im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2662             gfx_add_point(im, im->ximg, 0);
2663             gfx_add_point(im, im->ximg, im->yimg);
2664             gfx_add_point(im, 0, im->yimg);
2665             gfx_close_path(im);
2666     }
2667     if (im->draw_x_grid == 1)
2668         vertical_grid(im);
2669     if (im->draw_y_grid == 1) {
2670         if (im->logarithmic) {
2671             res = horizontal_log_grid(im);
2672         } else {
2673             res = draw_horizontal_grid(im);
2674         }
2675
2676         /* dont draw horizontal grid if there is no min and max val */
2677         if (!res) {
2678             char     *nodata = "No Data found";
2679
2680             gfx_text(im, im->ximg / 2,
2681                      (2 * im->yorigin -
2682                       im->ysize) / 2,
2683                      im->graph_col[GRC_FONT],
2684                      im->
2685                      text_prop[TEXT_PROP_AXIS].
2686                      font_desc,
2687                      im->tabwidth, 0.0,
2688                      GFX_H_CENTER, GFX_V_CENTER, nodata);
2689         }
2690     }
2691
2692     /* yaxis unit description */
2693     if (im->ylegend[0] != '\0'){
2694         gfx_text(im,
2695                  im->xOriginLegendY+10,
2696                  im->yOriginLegendY,
2697                  im->graph_col[GRC_FONT],
2698                  im->
2699                  text_prop[TEXT_PROP_UNIT].
2700                  font_desc,
2701                  im->tabwidth,
2702                  RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2703
2704     }
2705     if (im->second_axis_legend[0] != '\0'){
2706             gfx_text( im,
2707                   im->xOriginLegendY2+10,
2708                   im->yOriginLegendY2,
2709                   im->graph_col[GRC_FONT],
2710                   im->text_prop[TEXT_PROP_UNIT].font_desc,
2711                   im->tabwidth,
2712                   RRDGRAPH_YLEGEND_ANGLE,
2713                   GFX_H_CENTER, GFX_V_CENTER,
2714                   im->second_axis_legend);
2715     }
2716
2717     /* graph title */
2718     gfx_text(im,
2719              im->xOriginTitle, im->yOriginTitle+6,
2720              im->graph_col[GRC_FONT],
2721              im->
2722              text_prop[TEXT_PROP_TITLE].
2723              font_desc,
2724              im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2725     /* rrdtool 'logo' */
2726     if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2727         water_color = im->graph_col[GRC_FONT];
2728         water_color.alpha = 0.3;
2729         double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2730         gfx_text(im, xpos, 5,
2731                  water_color,
2732                  im->
2733                  text_prop[TEXT_PROP_WATERMARK].
2734                  font_desc, im->tabwidth,
2735                  -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2736     }
2737     /* graph watermark */
2738     if (im->watermark[0] != '\0') {
2739         water_color = im->graph_col[GRC_FONT];
2740         water_color.alpha = 0.3;
2741         gfx_text(im,
2742                  im->ximg / 2, im->yimg - 6,
2743                  water_color,
2744                  im->
2745                  text_prop[TEXT_PROP_WATERMARK].
2746                  font_desc, im->tabwidth, 0,
2747                  GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2748     }
2749
2750     /* graph labels */
2751     if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2752         for (i = 0; i < im->gdes_c; i++) {
2753             if (im->gdes[i].legend[0] == '\0')
2754                 continue;
2755             /* im->gdes[i].leg_y is the bottom of the legend */
2756             X0 = im->xOriginLegend + im->gdes[i].leg_x;
2757             Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2758             gfx_text(im, X0, Y0,
2759                      im->graph_col[GRC_FONT],
2760                      im->
2761                      text_prop
2762                      [TEXT_PROP_LEGEND].font_desc,
2763                      im->tabwidth, 0.0,
2764                      GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2765             /* The legend for GRAPH items starts with "M " to have
2766                enough space for the box */
2767             if (im->gdes[i].gf != GF_PRINT &&
2768                 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2769                 double    boxH, boxV;
2770                 double    X1, Y1;
2771
2772                 boxH = gfx_get_text_width(im, 0,
2773                                           im->
2774                                           text_prop
2775                                           [TEXT_PROP_LEGEND].
2776                                           font_desc,
2777                                           im->tabwidth, "o") * 1.2;
2778                 boxV = boxH;
2779                 /* shift the box up a bit */
2780                 Y0 -= boxV * 0.4;
2781
2782         if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */ 
2783                         cairo_save(im->cr);
2784                         cairo_new_path(im->cr);
2785                         cairo_set_line_width(im->cr, 1.0);
2786                         gfx_line(im,
2787                                 X0, Y0 - boxV / 2,
2788                                 X0 + boxH, Y0 - boxV / 2,
2789                                 1.0, im->gdes[i].col);
2790                         gfx_close_path(im);
2791                 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2792                         cairo_save(im->cr);
2793                         cairo_new_path(im->cr);
2794                         cairo_set_line_width(im->cr, 1.0);
2795                         gfx_line(im,
2796                                 X0 + boxH / 2, Y0,
2797                                 X0 + boxH / 2, Y0 - boxV,
2798                                 1.0, im->gdes[i].col);
2799                         gfx_close_path(im);
2800                 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2801                         cairo_save(im->cr);
2802                         cairo_new_path(im->cr);
2803                         cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2804                         gfx_line(im,
2805                                 X0, Y0,
2806                                 X0 + boxH, Y0 - boxV,
2807                                 im->gdes[i].linewidth, im->gdes[i].col);
2808                         gfx_close_path(im);
2809                 } else {
2810                 /* make sure transparent colors show up the same way as in the graph */
2811                         gfx_new_area(im,
2812                                      X0, Y0 - boxV,
2813                                      X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2814                         gfx_add_point(im, X0 + boxH, Y0 - boxV);
2815                         gfx_close_path(im);
2816                         gfx_new_area(im, X0, Y0 - boxV, X0,
2817                                      Y0, X0 + boxH, Y0, im->gdes[i].col);
2818                         gfx_add_point(im, X0 + boxH, Y0 - boxV);
2819                         gfx_close_path(im);
2820                         cairo_save(im->cr);
2821                         cairo_new_path(im->cr);
2822                         cairo_set_line_width(im->cr, 1.0);
2823                         X1 = X0 + boxH;
2824                         Y1 = Y0 - boxV;
2825                         gfx_line_fit(im, &X0, &Y0);
2826                         gfx_line_fit(im, &X1, &Y1);
2827                         cairo_move_to(im->cr, X0, Y0);
2828                         cairo_line_to(im->cr, X1, Y0);
2829                         cairo_line_to(im->cr, X1, Y1);
2830                         cairo_line_to(im->cr, X0, Y1);
2831                         cairo_close_path(im->cr);
2832                         cairo_set_source_rgba(im->cr,
2833                                               im->graph_col[GRC_FRAME].red,
2834                                               im->graph_col[GRC_FRAME].green,
2835                                               im->graph_col[GRC_FRAME].blue,
2836                                               im->graph_col[GRC_FRAME].alpha);
2837                 }
2838                 if (im->gdes[i].dash) {
2839                     /* make box borders in legend dashed if the graph is dashed */
2840                     double    dashes[] = {
2841                         3.0
2842                     };
2843                     cairo_set_dash(im->cr, dashes, 1, 0.0);
2844                 }
2845                 cairo_stroke(im->cr);
2846                 cairo_restore(im->cr);
2847             }
2848         }
2849     }
2850 }
2851
2852
2853 /*****************************************************
2854  * lazy check make sure we rely need to create this graph
2855  *****************************************************/
2856
2857 int lazy_check(
2858     image_desc_t *im)
2859 {
2860     FILE     *fd = NULL;
2861     int       size = 1;
2862     struct stat imgstat;
2863
2864     if (im->lazy == 0)
2865         return 0;       /* no lazy option */
2866     if (strlen(im->graphfile) == 0)
2867         return 0;       /* inmemory option */
2868     if (stat(im->graphfile, &imgstat) != 0)
2869         return 0;       /* can't stat */
2870     /* one pixel in the existing graph is more then what we would
2871        change here ... */
2872     if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2873         return 0;
2874     if ((fd = fopen(im->graphfile, "rb")) == NULL)
2875         return 0;       /* the file does not exist */
2876     switch (im->imgformat) {
2877     case IF_PNG:
2878         size = PngSize(fd, &(im->ximg), &(im->yimg));
2879         break;
2880     default:
2881         size = 1;
2882     }
2883     fclose(fd);
2884     return size;
2885 }
2886
2887
2888 int graph_size_location(
2889     image_desc_t
2890     *im,
2891     int elements)
2892 {
2893     /* The actual size of the image to draw is determined from
2894      ** several sources.  The size given on the command line is
2895      ** the graph area but we need more as we have to draw labels
2896      ** and other things outside the graph area. If the option
2897      ** --full-size-mode is selected the size defines the total
2898      ** image size and the size available for the graph is
2899      ** calculated.
2900      */
2901
2902     /** +---+-----------------------------------+
2903      ** | y |...............graph title.........|
2904      ** |   +---+-------------------------------+
2905      ** | a | y |                               |
2906      ** | x |   |                               |
2907      ** | i | a |                               |
2908      ** | s | x |       main graph area         |
2909      ** |   | i |                               |
2910      ** | t | s |                               |
2911      ** | i |   |                               |
2912      ** | t | l |                               |
2913      ** | l | b +-------------------------------+
2914      ** | e | l |       x axis labels           |
2915      ** +---+---+-------------------------------+
2916      ** |....................legends............|
2917      ** +---------------------------------------+
2918      ** |                   watermark           |
2919      ** +---------------------------------------+
2920      */
2921
2922     int       Xvertical = 0, Xvertical2 = 0, Ytitle =
2923         0, Xylabel = 0, Xmain = 0, Ymain =
2924         0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2925
2926     // no legends and no the shall be plotted it's easy
2927     if (im->extra_flags & ONLY_GRAPH) {
2928         im->xorigin = 0;
2929         im->ximg = im->xsize;
2930         im->yimg = im->ysize;
2931         im->yorigin = im->ysize;
2932         ytr(im, DNAN);
2933         return 0;
2934     }
2935
2936     if(im->watermark[0] != '\0') {
2937         Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2938     }
2939
2940     // calculate the width of the left vertical legend
2941     if (im->ylegend[0] != '\0') {
2942         Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2943     }
2944
2945     // calculate the width of the right vertical legend
2946     if (im->second_axis_legend[0] != '\0') {
2947         Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2948     }
2949     else{
2950         Xvertical2 = Xspacing;
2951     }
2952
2953     if (im->title[0] != '\0') {
2954         /* The title is placed "inbetween" two text lines so it
2955          ** automatically has some vertical spacing.  The horizontal
2956          ** spacing is added here, on each side.
2957          */
2958         /* if necessary, reduce the font size of the title until it fits the image width */
2959         Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2960     }
2961     else{
2962         // we have no title; get a little clearing from the top
2963         Ytitle = 1.5 * Yspacing;
2964     }
2965
2966     if (elements) {
2967         if (im->draw_x_grid) {
2968             // calculate the height of the horizontal labelling
2969             Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2970         }
2971         if (im->draw_y_grid || im->forceleftspace) {
2972             // calculate the width of the vertical labelling
2973             Xylabel =
2974                 gfx_get_text_width(im, 0,
2975                                    im->text_prop[TEXT_PROP_AXIS].font_desc,
2976                                    im->tabwidth, "0") * im->unitslength;
2977         }
2978     }
2979
2980     // add some space to the labelling
2981     Xylabel += Xspacing;
2982
2983     /* If the legend is printed besides the graph the width has to be
2984      ** calculated first. Placing the legend north or south of the
2985      ** graph requires the width calculation first, so the legend is
2986      ** skipped for the moment.
2987      */
2988     im->legendheight = 0;
2989     im->legendwidth = 0;
2990     if (!(im->extra_flags & NOLEGEND)) {
2991         if(im->legendposition == WEST || im->legendposition == EAST){
2992             if (leg_place(im, 1) == -1){
2993                 return -1;
2994             }
2995         }
2996     }
2997
2998     if (im->extra_flags & FULL_SIZE_MODE) {
2999
3000         /* The actual size of the image to draw has been determined by the user.
3001          ** The graph area is the space remaining after accounting for the legend,
3002          ** the watermark, the axis labels, and the title.
3003          */
3004         im->ximg = im->xsize;
3005         im->yimg = im->ysize;
3006         Xmain = im->ximg;
3007         Ymain = im->yimg;
3008
3009         /* Now calculate the total size.  Insert some spacing where
3010            desired.  im->xorigin and im->yorigin need to correspond
3011            with the lower left corner of the main graph area or, if
3012            this one is not set, the imaginary box surrounding the
3013            pie chart area. */
3014         /* Initial size calculation for the main graph area */
3015
3016         Xmain -= Xylabel;// + Xspacing;
3017         if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3018             Xmain -= im->legendwidth;// + Xspacing;
3019         }
3020         if (im->second_axis_scale != 0){
3021             Xmain -= Xylabel;
3022         }
3023         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3024             Xmain -= Xspacing;
3025         }
3026
3027         Xmain -= Xvertical + Xvertical2;
3028
3029         /* limit the remaining space to 0 */
3030         if(Xmain < 1){
3031             Xmain = 1;
3032         }
3033         im->xsize = Xmain;
3034
3035         /* Putting the legend north or south, the height can now be calculated */
3036         if (!(im->extra_flags & NOLEGEND)) {
3037             if(im->legendposition == NORTH || im->legendposition == SOUTH){
3038                 im->legendwidth = im->ximg;
3039                 if (leg_place(im, 0) == -1){
3040                     return -1;
3041                 }
3042             }
3043         }
3044
3045         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3046             Ymain -=  Yxlabel + im->legendheight;
3047         }
3048         else{
3049             Ymain -= Yxlabel;
3050         }
3051
3052         /* reserve space for the title *or* some padding above the graph */
3053         Ymain -= Ytitle;
3054
3055             /* reserve space for padding below the graph */
3056         if (im->extra_flags & NOLEGEND) {
3057             Ymain -= Yspacing;
3058         }
3059
3060         if (im->watermark[0] != '\0') {
3061             Ymain -= Ywatermark;
3062         }
3063         /* limit the remaining height to 0 */
3064         if(Ymain < 1){
3065             Ymain = 1;
3066         }
3067         im->ysize = Ymain;
3068     } else {            /* dimension options -width and -height refer to the dimensions of the main graph area */
3069
3070         /* The actual size of the image to draw is determined from
3071          ** several sources.  The size given on the command line is
3072          ** the graph area but we need more as we have to draw labels
3073          ** and other things outside the graph area.
3074          */
3075
3076         if (elements) {
3077             Xmain = im->xsize; // + Xspacing;
3078             Ymain = im->ysize;
3079         }
3080
3081         im->ximg = Xmain + Xylabel;
3082         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3083             im->ximg += Xspacing;
3084         }
3085
3086         if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3087             im->ximg += im->legendwidth;// + Xspacing;
3088         }
3089         if (im->second_axis_scale != 0){
3090             im->ximg += Xylabel;
3091         }
3092
3093         im->ximg += Xvertical + Xvertical2;
3094
3095         if (!(im->extra_flags & NOLEGEND)) {
3096             if(im->legendposition == NORTH || im->legendposition == SOUTH){
3097                 im->legendwidth = im->ximg;
3098                 if (leg_place(im, 0) == -1){
3099                     return -1;
3100                 }
3101             }
3102         }
3103
3104         im->yimg = Ymain + Yxlabel;
3105         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3106              im->yimg += im->legendheight;
3107         }
3108
3109         /* reserve space for the title *or* some padding above the graph */
3110         if (Ytitle) {
3111             im->yimg += Ytitle;
3112         } else {
3113             im->yimg += 1.5 * Yspacing;
3114         }
3115         /* reserve space for padding below the graph */
3116         if (im->extra_flags & NOLEGEND) {
3117             im->yimg += Yspacing;
3118         }
3119
3120         if (im->watermark[0] != '\0') {
3121             im->yimg += Ywatermark;
3122         }
3123     }
3124
3125
3126     /* In case of putting the legend in west or east position the first
3127      ** legend calculation might lead to wrong positions if some items
3128      ** are not aligned on the left hand side (e.g. centered) as the
3129      ** legendwidth wight have been increased after the item was placed.
3130      ** In this case the positions have to be recalculated.
3131      */
3132     if (!(im->extra_flags & NOLEGEND)) {
3133         if(im->legendposition == WEST || im->legendposition == EAST){
3134             if (leg_place(im, 0) == -1){
3135                 return -1;
3136             }
3137         }
3138     }
3139
3140     /* After calculating all dimensions
3141      ** it is now possible to calculate
3142      ** all offsets.
3143      */
3144     switch(im->legendposition){
3145         case NORTH:
3146             im->xOriginTitle   = Xvertical + Xylabel + (im->xsize / 2);
3147             im->yOriginTitle   = 0;
3148
3149             im->xOriginLegend  = 0;
3150             im->yOriginLegend  = Ytitle;
3151
3152             im->xOriginLegendY = 0;
3153             im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3154
3155             im->xorigin        = Xvertical + Xylabel;
3156             im->yorigin        = Ytitle + im->legendheight + Ymain;
3157
3158             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3159             if (im->second_axis_scale != 0){
3160                 im->xOriginLegendY2 += Xylabel;
3161             }
3162             im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3163
3164             break;
3165
3166         case WEST:
3167             im->xOriginTitle   = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3168             im->yOriginTitle   = 0;
3169
3170             im->xOriginLegend  = 0;
3171             im->yOriginLegend  = Ytitle;
3172
3173             im->xOriginLegendY = im->legendwidth;
3174             im->yOriginLegendY = Ytitle + (Ymain / 2);
3175
3176             im->xorigin        = im->legendwidth + Xvertical + Xylabel;
3177             im->yorigin        = Ytitle + Ymain;
3178
3179             im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3180             if (im->second_axis_scale != 0){
3181                 im->xOriginLegendY2 += Xylabel;
3182             }
3183             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3184
3185             break;
3186
3187         case SOUTH:
3188             im->xOriginTitle   = Xvertical + Xylabel + im->xsize / 2;
3189             im->yOriginTitle   = 0;
3190
3191             im->xOriginLegend  = 0;
3192             im->yOriginLegend  = Ytitle + Ymain + Yxlabel;
3193
3194             im->xOriginLegendY = 0;
3195             im->yOriginLegendY = Ytitle + (Ymain / 2);
3196
3197             im->xorigin        = Xvertical + Xylabel;
3198             im->yorigin        = Ytitle + Ymain;
3199
3200             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3201             if (im->second_axis_scale != 0){
3202                 im->xOriginLegendY2 += Xylabel;
3203             }
3204             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3205
3206             break;
3207
3208         case EAST:
3209             im->xOriginTitle   = Xvertical + Xylabel + im->xsize / 2;
3210             im->yOriginTitle   = 0;
3211
3212             im->xOriginLegend  = Xvertical + Xylabel + Xmain + Xvertical2;
3213             if (im->second_axis_scale != 0){
3214                 im->xOriginLegend += Xylabel;
3215             }
3216             im->yOriginLegend  = Ytitle;
3217
3218             im->xOriginLegendY = 0;
3219             im->yOriginLegendY = Ytitle + (Ymain / 2);
3220
3221             im->xorigin        = Xvertical + Xylabel;
3222             im->yorigin        = Ytitle + Ymain;
3223
3224             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3225             if (im->second_axis_scale != 0){
3226                 im->xOriginLegendY2 += Xylabel;
3227             }
3228             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3229
3230             if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3231                 im->xOriginTitle    += Xspacing;
3232                 im->xOriginLegend   += Xspacing;
3233                 im->xOriginLegendY  += Xspacing;
3234                 im->xorigin         += Xspacing;
3235                 im->xOriginLegendY2 += Xspacing;
3236             }
3237             break;
3238     }
3239
3240     xtr(im, 0);
3241     ytr(im, DNAN);
3242     return 0;
3243 }
3244
3245 static cairo_status_t cairo_output(
3246     void *closure,
3247     const unsigned char
3248     *data,
3249     unsigned int length)
3250 {
3251     image_desc_t *im = (image_desc_t*)closure;
3252
3253     im->rendered_image =
3254         (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3255     if (im->rendered_image == NULL)
3256         return CAIRO_STATUS_WRITE_ERROR;
3257     memcpy(im->rendered_image + im->rendered_image_size, data, length);
3258     im->rendered_image_size += length;
3259     return CAIRO_STATUS_SUCCESS;
3260 }
3261
3262 /* draw that picture thing ... */
3263 int graph_paint(
3264     image_desc_t *im)
3265 {
3266     int       i, ii;
3267     int       lazy = lazy_check(im);
3268     double    areazero = 0.0;
3269     graph_desc_t *lastgdes = NULL;
3270     rrd_infoval_t info;
3271
3272 //    PangoFontMap *font_map = pango_cairo_font_map_get_default();
3273
3274     /* pull the data from the rrd files ... */
3275     if (data_fetch(im) == -1)
3276         return -1;
3277     /* evaluate VDEF and CDEF operations ... */
3278     if (data_calc(im) == -1)
3279         return -1;
3280     /* calculate and PRINT and GPRINT definitions. We have to do it at
3281      * this point because it will affect the length of the legends
3282      * if there are no graph elements (i==0) we stop here ...
3283      * if we are lazy, try to quit ...
3284      */
3285     i = print_calc(im);
3286     if (i < 0)
3287         return -1;
3288
3289     /* if we want and can be lazy ... quit now */
3290     if (i == 0)
3291         return 0;
3292
3293 /**************************************************************
3294  *** Calculating sizes and locations became a bit confusing ***
3295  *** so I moved this into a separate function.              ***
3296  **************************************************************/
3297     if (graph_size_location(im, i) == -1)
3298         return -1;
3299
3300     info.u_cnt = im->xorigin;
3301     grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3302     info.u_cnt = im->yorigin - im->ysize;
3303     grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3304     info.u_cnt = im->xsize;
3305     grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3306     info.u_cnt = im->ysize;
3307     grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3308     info.u_cnt = im->ximg;
3309     grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3310     info.u_cnt = im->yimg;
3311     grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3312     info.u_cnt = im->start;
3313     grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3314     info.u_cnt = im->end;
3315     grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3316
3317     /* if we want and can be lazy ... quit now */
3318     if (lazy)
3319         return 0;
3320
3321     /* get actual drawing data and find min and max values */
3322     if (data_proc(im) == -1)
3323         return -1;
3324     if (!im->logarithmic) {
3325         si_unit(im);
3326     }
3327
3328     /* identify si magnitude Kilo, Mega Giga ? */
3329     if (!im->rigid && !im->logarithmic)
3330         expand_range(im);   /* make sure the upper and lower limit are
3331                                sensible values */
3332
3333     info.u_val = im->minval;
3334     grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3335     info.u_val = im->maxval;
3336     grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3337
3338
3339     if (!calc_horizontal_grid(im))
3340         return -1;
3341     /* reset precalc */
3342     ytr(im, DNAN);
3343 /*   if (im->gridfit)
3344      apply_gridfit(im); */
3345     /* the actual graph is created by going through the individual
3346        graph elements and then drawing them */
3347     cairo_surface_destroy(im->surface);
3348     switch (im->imgformat) {
3349     case IF_PNG:
3350         im->surface =
3351             cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3352                                        im->ximg * im->zoom,
3353                                        im->yimg * im->zoom);
3354         break;
3355     case IF_PDF:
3356         im->gridfit = 0;
3357         im->surface = strlen(im->graphfile)
3358             ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3359                                        im->yimg * im->zoom)
3360             : cairo_pdf_surface_create_for_stream
3361             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3362         break;
3363     case IF_EPS:
3364         im->gridfit = 0;
3365         im->surface = strlen(im->graphfile)
3366             ?
3367             cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3368                                     im->yimg * im->zoom)
3369             : cairo_ps_surface_create_for_stream
3370             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3371         break;
3372     case IF_SVG:
3373         im->gridfit = 0;
3374         im->surface = strlen(im->graphfile)
3375             ?
3376             cairo_svg_surface_create(im->
3377                                      graphfile,
3378                                      im->ximg * im->zoom, im->yimg * im->zoom)
3379             : cairo_svg_surface_create_for_stream
3380             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3381         cairo_svg_surface_restrict_to_version
3382             (im->surface, CAIRO_SVG_VERSION_1_1);
3383         break;
3384     };
3385     cairo_destroy(im->cr);
3386     im->cr = cairo_create(im->surface);
3387     cairo_set_antialias(im->cr, im->graph_antialias);
3388     cairo_scale(im->cr, im->zoom, im->zoom);
3389 //    pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3390     gfx_new_area(im, 0, 0, 0, im->yimg,
3391                  im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3392     gfx_add_point(im, im->ximg, 0);
3393     gfx_close_path(im);
3394     gfx_new_area(im, im->xorigin,
3395                  im->yorigin,
3396                  im->xorigin +
3397                  im->xsize, im->yorigin,
3398                  im->xorigin +
3399                  im->xsize,
3400                  im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3401     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3402     gfx_close_path(im);
3403     cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3404                     im->xsize, im->ysize + 2.0);
3405     cairo_clip(im->cr);
3406     if (im->minval > 0.0)
3407         areazero = im->minval;
3408     if (im->maxval < 0.0)
3409         areazero = im->maxval;
3410     for (i = 0; i < im->gdes_c; i++) {
3411         switch (im->gdes[i].gf) {
3412         case GF_CDEF:
3413         case GF_VDEF:
3414         case GF_DEF:
3415         case GF_PRINT:
3416         case GF_GPRINT:
3417         case GF_COMMENT:
3418         case GF_TEXTALIGN:
3419         case GF_HRULE:
3420         case GF_VRULE:
3421         case GF_XPORT:
3422         case GF_SHIFT:
3423             break;
3424         case GF_TICK:
3425             for (ii = 0; ii < im->xsize; ii++) {
3426                 if (!isnan(im->gdes[i].p_data[ii])
3427                     && im->gdes[i].p_data[ii] != 0.0) {
3428                     if (im->gdes[i].yrule > 0) {
3429                         gfx_line(im,
3430                                  im->xorigin + ii,
3431                                  im->yorigin + 1.0,
3432                                  im->xorigin + ii,
3433                                  im->yorigin -
3434                                  im->gdes[i].yrule *
3435                                  im->ysize, 1.0, im->gdes[i].col);
3436                     } else if (im->gdes[i].yrule < 0) {
3437                         gfx_line(im,
3438                                  im->xorigin + ii,
3439                                  im->yorigin - im->ysize - 1.0,
3440                                  im->xorigin + ii,
3441                                  im->yorigin - im->ysize -
3442                                                 im->gdes[i].
3443                                                 yrule *
3444                                  im->ysize, 1.0, im->gdes[i].col);
3445                     }
3446                 }
3447             }
3448             break;
3449         case GF_LINE:
3450         case GF_AREA:
3451         case GF_GRAD: {
3452             rrd_value_t diffval = im->maxval - im->minval;
3453             rrd_value_t maxlimit = im->maxval + 9 * diffval;
3454             rrd_value_t minlimit = im->minval - 9 * diffval;        
3455             for (ii = 0; ii < im->xsize; ii++) {
3456                 /* fix data points at oo and -oo */
3457                 if (isinf(im->gdes[i].p_data[ii])) {
3458                     if (im->gdes[i].p_data[ii] > 0) {
3459                         im->gdes[i].p_data[ii] = im->maxval;
3460                     } else {
3461                         im->gdes[i].p_data[ii] = im->minval;
3462                     }
3463                 }
3464                 /* some versions of cairo go unstable when trying
3465                    to draw way out of the canvas ... lets not even try */
3466                if (im->gdes[i].p_data[ii] > maxlimit) {
3467                    im->gdes[i].p_data[ii] = maxlimit;
3468                }
3469                if (im->gdes[i].p_data[ii] < minlimit) {
3470                    im->gdes[i].p_data[ii] = minlimit;
3471                }
3472             }           /* for */
3473
3474             /* *******************************************************
3475                a           ___. (a,t)
3476                |   |    ___
3477                ____|   |   |   |
3478                |       |___|
3479                -------|--t-1--t--------------------------------
3480
3481                if we know the value at time t was a then
3482                we draw a square from t-1 to t with the value a.
3483
3484                ********************************************************* */
3485             if (im->gdes[i].col.alpha != 0.0) {
3486                 /* GF_LINE and friend */
3487                 if (im->gdes[i].gf == GF_LINE) {
3488                     double    last_y = 0.0;
3489                     int       draw_on = 0;
3490
3491                     cairo_save(im->cr);
3492                     cairo_new_path(im->cr);
3493                     cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3494                     if (im->gdes[i].dash) {
3495                         cairo_set_dash(im->cr,
3496                                        im->gdes[i].p_dashes,
3497                                        im->gdes[i].ndash, im->gdes[i].offset);
3498                     }
3499
3500                     for (ii = 1; ii < im->xsize; ii++) {
3501                         if (isnan(im->gdes[i].p_data[ii])
3502                             || (im->slopemode == 1
3503                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3504                             draw_on = 0;
3505                             continue;
3506                         }
3507                         if (draw_on == 0) {
3508                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3509                             if (im->slopemode == 0) {
3510                                 double    x = ii - 1 + im->xorigin;
3511                                 double    y = last_y;
3512
3513                                 gfx_line_fit(im, &x, &y);
3514                                 cairo_move_to(im->cr, x, y);
3515                                 x = ii + im->xorigin;
3516                                 y = last_y;
3517                                 gfx_line_fit(im, &x, &y);
3518                                 cairo_line_to(im->cr, x, y);
3519                             } else {
3520                                 double    x = ii - 1 + im->xorigin;
3521                                 double    y =
3522                                     ytr(im, im->gdes[i].p_data[ii - 1]);
3523                                 gfx_line_fit(im, &x, &y);
3524                                 cairo_move_to(im->cr, x, y);
3525                                 x = ii + im->xorigin;
3526                                 y = last_y;
3527                                 gfx_line_fit(im, &x, &y);
3528                                 cairo_line_to(im->cr, x, y);
3529                             }
3530                             draw_on = 1;
3531                         } else {
3532                             double    x1 = ii + im->xorigin;
3533                             double    y1 = ytr(im, im->gdes[i].p_data[ii]);
3534
3535                             if (im->slopemode == 0
3536                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3537                                 double    x = ii - 1 + im->xorigin;
3538                                 double    y = y1;
3539
3540                                 gfx_line_fit(im, &x, &y);
3541                                 cairo_line_to(im->cr, x, y);
3542                             };
3543                             last_y = y1;
3544                             gfx_line_fit(im, &x1, &y1);
3545                             cairo_line_to(im->cr, x1, y1);
3546                         };
3547                     }
3548                     cairo_set_source_rgba(im->cr,
3549                                           im->gdes[i].
3550                                           col.red,
3551                                           im->gdes[i].
3552                                           col.green,
3553                                           im->gdes[i].
3554                                           col.blue, im->gdes[i].col.alpha);
3555                     cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3556                     cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3557                     cairo_stroke(im->cr);
3558                     cairo_restore(im->cr);
3559                 } else {
3560                                         double lastx=0;
3561                                         double lasty=0;
3562                     int       idxI = -1;
3563                     double   *foreY =
3564                         (double *) malloc(sizeof(double) * im->xsize * 2);
3565                     double   *foreX =
3566                         (double *) malloc(sizeof(double) * im->xsize * 2);
3567                     double   *backY =
3568                         (double *) malloc(sizeof(double) * im->xsize * 2);
3569                     double   *backX =
3570                         (double *) malloc(sizeof(double) * im->xsize * 2);
3571                     int       drawem = 0;
3572
3573                     for (ii = 0; ii <= im->xsize; ii++) {
3574                         double    ybase, ytop;
3575
3576                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3577                             int       cntI = 1;
3578                             int       lastI = 0;
3579
3580                             while (cntI < idxI
3581                                    &&
3582                                    AlmostEqual2sComplement(foreY
3583                                                            [lastI],
3584                                                            foreY[cntI], 4)
3585                                    &&
3586                                    AlmostEqual2sComplement(foreY
3587                                                            [lastI],
3588                                                            foreY
3589                                                            [cntI + 1], 4)) {
3590                                 cntI++;
3591                             }
3592                                                         if (im->gdes[i].gf != GF_GRAD) {
3593                                 gfx_new_area(im,
3594                                              backX[0], backY[0],
3595                                              foreX[0], foreY[0],
3596                                              foreX[cntI],
3597                                              foreY[cntI], im->gdes[i].col);
3598                                                         } else {
3599                                                                 lastx = foreX[cntI];
3600                                                                 lasty = foreY[cntI];
3601                                                         }
3602                                                         while (cntI < idxI) {
3603                                 lastI = cntI;
3604                                 cntI++;
3605                                 while (cntI < idxI
3606                                        &&
3607                                        AlmostEqual2sComplement(foreY
3608                                                                [lastI],
3609                                                                foreY[cntI], 4)
3610                                        &&
3611                                        AlmostEqual2sComplement(foreY
3612                                                                [lastI],
3613                                                                foreY
3614                                                                [cntI
3615                                                                 + 1], 4)) {
3616                                     cntI++;
3617                                 }
3618                                                                 if (im->gdes[i].gf != GF_GRAD) {
3619                                         gfx_add_point(im, foreX[cntI], foreY[cntI]);
3620                                                                 } else {
3621                                                                         gfx_add_rect_fadey(im, 
3622                                                                                 lastx, foreY[0],
3623                                                                                 foreX[cntI], foreY[cntI], lasty, 
3624                                                                                 im->gdes[i].col,
3625                                                                                 im->gdes[i].col2,
3626                                                                                 im->gdes[i].gradheight
3627                                                                                 );
3628                                                                         lastx = foreX[cntI];
3629                                                                         lasty = foreY[cntI];
3630                                                                 }
3631                             }
3632                                                         if (im->gdes[i].gf != GF_GRAD) {
3633                                 gfx_add_point(im, backX[idxI], backY[idxI]);
3634                                                         } else {
3635                                                                 gfx_add_rect_fadey(im,
3636                                                                         lastx, foreY[0],
3637                                                                         backX[idxI], backY[idxI], lasty,
3638                                                                         im->gdes[i].col,
3639                                                                         im->gdes[i].col2,
3640                                                                         im->gdes[i].gradheight);
3641                                                                 lastx = backX[idxI];
3642                                                                 lasty = backY[idxI];
3643                                                         }
3644                             while (idxI > 1) {
3645                                 lastI = idxI;
3646                                 idxI--;
3647                                 while (idxI > 1
3648                                        &&
3649                                        AlmostEqual2sComplement(backY
3650                                                                [lastI],
3651                                                                backY[idxI], 4)
3652                                        &&
3653                                        AlmostEqual2sComplement(backY
3654                                                                [lastI],
3655                                                                backY
3656                                                                [idxI
3657                                                                 - 1], 4)) {
3658                                     idxI--;
3659                                 }
3660                                                                 if (im->gdes[i].gf != GF_GRAD) {
3661                                         gfx_add_point(im, backX[idxI], backY[idxI]);
3662                                                                 } else {
3663                                                                         gfx_add_rect_fadey(im,
3664                                                                                 lastx, foreY[0],
3665                                                                                 backX[idxI], backY[idxI], lasty,
3666                                                                                 im->gdes[i].col,
3667                                                                                 im->gdes[i].col2,
3668                                                                                 im->gdes[i].gradheight);
3669                                                                         lastx = backX[idxI];
3670                                                                         lasty = backY[idxI];
3671                                                                 }
3672                             }
3673                             idxI = -1;
3674                             drawem = 0;
3675                                                         if (im->gdes[i].gf != GF_GRAD) 
3676                                     gfx_close_path(im);
3677                         }
3678                         if (drawem != 0) {
3679                             drawem = 0;
3680                             idxI = -1;
3681                         }
3682                         if (ii == im->xsize)
3683                             break;
3684                         if (im->slopemode == 0 && ii == 0) {
3685                             continue;
3686                         }
3687                         if (isnan(im->gdes[i].p_data[ii])) {
3688                             drawem = 1;
3689                             continue;
3690                         }
3691                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3692                         if (lastgdes && im->gdes[i].stack) {
3693                             ybase = ytr(im, lastgdes->p_data[ii]);
3694                         } else {
3695                             ybase = ytr(im, areazero);
3696                         }
3697                         if (ybase == ytop) {
3698                             drawem = 1;
3699                             continue;
3700                         }
3701
3702                         if (ybase > ytop) {
3703                             double    extra = ytop;
3704
3705                             ytop = ybase;
3706                             ybase = extra;
3707                         }
3708                         if (im->slopemode == 0) {
3709                             backY[++idxI] = ybase - 0.2;
3710                             backX[idxI] = ii + im->xorigin - 1;
3711                             foreY[idxI] = ytop + 0.2;
3712                             foreX[idxI] = ii + im->xorigin - 1;
3713                         }
3714                         backY[++idxI] = ybase - 0.2;
3715                         backX[idxI] = ii + im->xorigin;
3716                         foreY[idxI] = ytop + 0.2;
3717                         foreX[idxI] = ii + im->xorigin;
3718                     }
3719                     /* close up any remaining area */
3720                     free(foreY);
3721                     free(foreX);
3722                     free(backY);
3723                     free(backX);
3724                 }       /* else GF_LINE */
3725             }
3726             /* if color != 0x0 */
3727             /* make sure we do not run into trouble when stacking on NaN */
3728             for (ii = 0; ii < im->xsize; ii++) {
3729                 if (isnan(im->gdes[i].p_data[ii])) {
3730                     if (lastgdes && (im->gdes[i].stack)) {
3731                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3732                     } else {
3733                         im->gdes[i].p_data[ii] = areazero;
3734                     }
3735                 }
3736             }
3737             lastgdes = &(im->gdes[i]);
3738             break;
3739         } /* GF_AREA, GF_LINE, GF_GRAD */
3740         case GF_STACK:
3741             rrd_set_error
3742                 ("STACK should already be turned into LINE or AREA here");
3743             return -1;
3744             break;
3745         }               /* switch */
3746     }
3747     cairo_reset_clip(im->cr);
3748
3749     /* grid_paint also does the text */
3750     if (!(im->extra_flags & ONLY_GRAPH))
3751         grid_paint(im);
3752     if (!(im->extra_flags & ONLY_GRAPH))
3753         axis_paint(im);
3754     /* the RULES are the last thing to paint ... */
3755     for (i = 0; i < im->gdes_c; i++) {
3756
3757         switch (im->gdes[i].gf) {
3758         case GF_HRULE:
3759             if (im->gdes[i].yrule >= im->minval
3760                 && im->gdes[i].yrule <= im->maxval) {
3761                 cairo_save(im->cr);
3762                 if (im->gdes[i].dash) {
3763                     cairo_set_dash(im->cr,
3764                                    im->gdes[i].p_dashes,
3765                                    im->gdes[i].ndash, im->gdes[i].offset);
3766                 }
3767                 gfx_line(im, im->xorigin,
3768                          ytr(im, im->gdes[i].yrule),
3769                          im->xorigin + im->xsize,
3770                          ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3771                 cairo_stroke(im->cr);
3772                 cairo_restore(im->cr);
3773             }
3774             break;
3775         case GF_VRULE:
3776             if (im->gdes[i].xrule >= im->start
3777                 && im->gdes[i].xrule <= im->end) {
3778                 cairo_save(im->cr);
3779                 if (im->gdes[i].dash) {
3780                     cairo_set_dash(im->cr,
3781                                    im->gdes[i].p_dashes,
3782                                    im->gdes[i].ndash, im->gdes[i].offset);
3783                 }
3784                 gfx_line(im,
3785                          xtr(im, im->gdes[i].xrule),
3786                          im->yorigin, xtr(im,
3787                                           im->
3788                                           gdes[i].
3789                                           xrule),
3790                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3791                 cairo_stroke(im->cr);
3792                 cairo_restore(im->cr);
3793             }
3794             break;
3795         default:
3796             break;
3797         }
3798     }
3799
3800
3801     switch (im->imgformat) {
3802     case IF_PNG:
3803     {
3804         cairo_status_t status;
3805
3806         status = strlen(im->graphfile) ?
3807             cairo_surface_write_to_png(im->surface, im->graphfile)
3808             : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3809                                                 im);
3810
3811         if (status != CAIRO_STATUS_SUCCESS) {
3812             rrd_set_error("Could not save png to '%s'", im->graphfile);
3813             return 1;
3814         }
3815         break;
3816     }
3817     default:
3818         if (strlen(im->graphfile)) {
3819             cairo_show_page(im->cr);
3820         } else {
3821             cairo_surface_finish(im->surface);
3822         }
3823         break;
3824     }
3825
3826     return 0;
3827 }
3828
3829
3830 /*****************************************************
3831  * graph stuff
3832  *****************************************************/
3833
3834 int gdes_alloc(
3835     image_desc_t *im)
3836 {
3837
3838     im->gdes_c++;
3839     if ((im->gdes = (graph_desc_t *)
3840          rrd_realloc(im->gdes, (im->gdes_c)
3841                      * sizeof(graph_desc_t))) == NULL) {
3842         rrd_set_error("realloc graph_descs");
3843         return -1;
3844     }
3845
3846
3847     im->gdes[im->gdes_c - 1].step = im->step;
3848     im->gdes[im->gdes_c - 1].step_orig = im->step;
3849     im->gdes[im->gdes_c - 1].stack = 0;
3850     im->gdes[im->gdes_c - 1].linewidth = 0;
3851     im->gdes[im->gdes_c - 1].debug = 0;
3852     im->gdes[im->gdes_c - 1].start = im->start;
3853     im->gdes[im->gdes_c - 1].start_orig = im->start;
3854     im->gdes[im->gdes_c - 1].end = im->end;
3855     im->gdes[im->gdes_c - 1].end_orig = im->end;
3856     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3857     im->gdes[im->gdes_c - 1].data = NULL;
3858     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3859     im->gdes[im->gdes_c - 1].data_first = 0;
3860     im->gdes[im->gdes_c - 1].p_data = NULL;
3861     im->gdes[im->gdes_c - 1].rpnp = NULL;
3862     im->gdes[im->gdes_c - 1].p_dashes = NULL;
3863     im->gdes[im->gdes_c - 1].shift = 0.0;
3864     im->gdes[im->gdes_c - 1].dash = 0;
3865     im->gdes[im->gdes_c - 1].ndash = 0;
3866     im->gdes[im->gdes_c - 1].offset = 0;
3867     im->gdes[im->gdes_c - 1].col.red = 0.0;
3868     im->gdes[im->gdes_c - 1].col.green = 0.0;
3869     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3870     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3871     im->gdes[im->gdes_c - 1].col2.red = 0.0;
3872     im->gdes[im->gdes_c - 1].col2.green = 0.0;
3873     im->gdes[im->gdes_c - 1].col2.blue = 0.0;
3874     im->gdes[im->gdes_c - 1].col2.alpha = 0.0;
3875     im->gdes[im->gdes_c - 1].gradheight = 50.0;
3876     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3877     im->gdes[im->gdes_c - 1].format[0] = '\0';
3878     im->gdes[im->gdes_c - 1].strftm = 0;
3879     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3880     im->gdes[im->gdes_c - 1].ds = -1;
3881     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3882     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3883     im->gdes[im->gdes_c - 1].yrule = DNAN;
3884     im->gdes[im->gdes_c - 1].xrule = 0;
3885     im->gdes[im->gdes_c - 1].daemon[0] = 0;
3886     return 0;
3887 }
3888
3889 /* copies input untill the first unescaped colon is found
3890    or until input ends. backslashes have to be escaped as well */
3891 int scan_for_col(
3892     const char *const input,
3893     int len,
3894     char *const output)
3895 {
3896     int       inp, outp = 0;
3897
3898     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3899         if (input[inp] == '\\'
3900             && input[inp + 1] != '\0'
3901             && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3902             output[outp++] = input[++inp];
3903         } else {
3904             output[outp++] = input[inp];
3905         }
3906     }
3907     output[outp] = '\0';
3908     return inp;
3909 }
3910
3911 /* Now just a wrapper around rrd_graph_v */
3912 int rrd_graph(
3913     int argc,
3914     char **argv,
3915     char ***prdata,
3916     int *xsize,
3917     int *ysize,
3918     FILE * stream,
3919     double *ymin,
3920     double *ymax)
3921 {
3922     int       prlines = 0;
3923     rrd_info_t *grinfo = NULL;
3924     rrd_info_t *walker;
3925
3926     grinfo = rrd_graph_v(argc, argv);
3927     if (grinfo == NULL)
3928         return -1;
3929     walker = grinfo;
3930     (*prdata) = NULL;
3931     while (walker) {
3932         if (strcmp(walker->key, "image_info") == 0) {
3933             prlines++;
3934             if (((*prdata) =
3935                  (char**)rrd_realloc((*prdata),
3936                              (prlines + 1) * sizeof(char *))) == NULL) {
3937                 rrd_set_error("realloc prdata");
3938                 return 0;
3939             }
3940             /* imginfo goes to position 0 in the prdata array */
3941             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3942                                              + 2) * sizeof(char));
3943             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3944             (*prdata)[prlines] = NULL;
3945         }
3946         /* skip anything else */
3947         walker = walker->next;
3948     }
3949     walker = grinfo;
3950     *xsize = 0;
3951     *ysize = 0;
3952     *ymin = 0;
3953     *ymax = 0;
3954     while (walker) {
3955         if (strcmp(walker->key, "image_width") == 0) {
3956             *xsize = walker->value.u_cnt;
3957         } else if (strcmp(walker->key, "image_height") == 0) {
3958             *ysize = walker->value.u_cnt;
3959         } else if (strcmp(walker->key, "value_min") == 0) {
3960             *ymin = walker->value.u_val;
3961         } else if (strcmp(walker->key, "value_max") == 0) {
3962             *ymax = walker->value.u_val;
3963         } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3964             prlines++;
3965             if (((*prdata) =
3966                  (char**)rrd_realloc((*prdata),
3967                              (prlines + 1) * sizeof(char *))) == NULL) {
3968                 rrd_set_error("realloc prdata");
3969                 return 0;
3970             }
3971             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3972                                              + 2) * sizeof(char));
3973             (*prdata)[prlines] = NULL;
3974             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3975         } else if (strcmp(walker->key, "image") == 0) {
3976             if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3977                    (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3978                 rrd_set_error("writing image");
3979                 return 0;
3980             }
3981         }
3982         /* skip anything else */
3983         walker = walker->next;
3984     }
3985     rrd_info_free(grinfo);
3986     return 0;
3987 }
3988
3989
3990 /* Some surgery done on this function, it became ridiculously big.
3991 ** Things moved:
3992 ** - initializing     now in rrd_graph_init()
3993 ** - options parsing  now in rrd_graph_options()
3994 ** - script parsing   now in rrd_graph_script()
3995 */
3996 rrd_info_t *rrd_graph_v(
3997     int argc,
3998     char **argv)
3999 {
4000     image_desc_t im;
4001     rrd_info_t *grinfo;
4002     char *old_locale;
4003     rrd_graph_init(&im);
4004     /* a dummy surface so that we can measure text sizes for placements */
4005     old_locale = setlocale(LC_NUMERIC, NULL);
4006     setlocale(LC_NUMERIC, "C");
4007     rrd_graph_options(argc, argv, &im);
4008     if (rrd_test_error()) {
4009         setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4010         rrd_info_free(im.grinfo);
4011         im_free(&im);
4012         return NULL;
4013     }
4014
4015     if (optind >= argc) {
4016         setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4017         rrd_info_free(im.grinfo);
4018         im_free(&im);
4019         rrd_set_error("missing filename");
4020         return NULL;
4021     }
4022
4023     if (strlen(argv[optind]) >= MAXPATH) {
4024         setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4025         rrd_set_error("filename (including path) too long");
4026         rrd_info_free(im.grinfo);
4027         im_free(&im);
4028         return NULL;
4029     }
4030
4031     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
4032     im.graphfile[MAXPATH - 1] = '\0';
4033
4034     if (strcmp(im.graphfile, "-") == 0) {
4035         im.graphfile[0] = '\0';
4036     }
4037
4038     rrd_graph_script(argc, argv, &im, 1);
4039     setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
4040
4041     if (rrd_test_error()) {
4042         rrd_info_free(im.grinfo);
4043         im_free(&im);
4044         return NULL;
4045     }
4046
4047     /* Everything is now read and the actual work can start */
4048
4049     if (graph_paint(&im) == -1) {
4050         rrd_info_free(im.grinfo);
4051         im_free(&im);
4052         return NULL;
4053     }
4054
4055
4056     /* The image is generated and needs to be output.
4057      ** Also, if needed, print a line with information about the image.
4058      */
4059
4060     if (im.imginfo) {
4061         rrd_infoval_t info;
4062         char     *path;
4063         char     *filename;
4064
4065         path = strdup(im.graphfile);
4066         filename = basename(path);
4067         info.u_str =
4068             sprintf_alloc(im.imginfo,
4069                           filename,
4070                           (long) (im.zoom *
4071                                   im.ximg), (long) (im.zoom * im.yimg));
4072         grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
4073         free(info.u_str);
4074         free(path);
4075     }
4076     if (im.rendered_image) {
4077         rrd_infoval_t img;
4078
4079         img.u_blo.size = im.rendered_image_size;
4080         img.u_blo.ptr = im.rendered_image;
4081         grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
4082     }
4083     grinfo = im.grinfo;
4084     im_free(&im);
4085     return grinfo;
4086 }
4087
4088 static void
4089 rrd_set_font_desc (
4090     image_desc_t *im,int prop,char *font, double size ){
4091     if (font){
4092         strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4093         im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4094         /* if we already got one, drop it first */
4095         pango_font_description_free(im->text_prop[prop].font_desc);
4096         im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4097     };
4098     if (size > 0){
4099         im->text_prop[prop].size = size;
4100     };
4101     if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4102         pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4103     };
4104 }
4105
4106 void rrd_graph_init(
4107     image_desc_t
4108     *im)
4109 {
4110     unsigned int i;
4111     char     *deffont = getenv("RRD_DEFAULT_FONT");
4112     static PangoFontMap *fontmap = NULL;
4113     PangoContext *context;
4114
4115 #ifdef HAVE_TZSET
4116     tzset();
4117 #endif
4118
4119     im->base = 1000;
4120     im->daemon_addr = NULL;
4121     im->draw_x_grid = 1;
4122     im->draw_y_grid = 1;
4123     im->draw_3d_border = 2;
4124     im->dynamic_labels = 0;
4125     im->extra_flags = 0;
4126     im->font_options = cairo_font_options_create();
4127     im->forceleftspace = 0;
4128     im->gdes_c = 0;
4129     im->gdes = NULL;
4130     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4131     im->grid_dash_off = 1;
4132     im->grid_dash_on = 1;
4133     im->gridfit = 1;
4134     im->grinfo = (rrd_info_t *) NULL;
4135     im->grinfo_current = (rrd_info_t *) NULL;
4136     im->imgformat = IF_PNG;
4137     im->imginfo = NULL;
4138     im->lazy = 0;
4139     im->legenddirection = TOP_DOWN;
4140     im->legendheight = 0;
4141     im->legendposition = SOUTH;
4142     im->legendwidth = 0;
4143     im->logarithmic = 0;
4144     im->maxval = DNAN;
4145     im->minval = 0;
4146     im->minval = DNAN;
4147     im->prt_c = 0;
4148     im->rigid = 0;
4149     im->rendered_image_size = 0;
4150     im->rendered_image = NULL;
4151     im->slopemode = 0;
4152     im->step = 0;
4153     im->symbol = ' ';
4154     im->tabwidth = 40.0;
4155     im->title[0] = '\0';
4156     im->unitsexponent = 9999;
4157     im->unitslength = 6;
4158     im->viewfactor = 1.0;
4159     im->watermark[0] = '\0';
4160     im->with_markup = 0;
4161     im->ximg = 0;
4162     im->xlab_user.minsec = -1;
4163     im->xorigin = 0;
4164     im->xOriginLegend = 0;
4165     im->xOriginLegendY = 0;
4166     im->xOriginLegendY2 = 0;
4167     im->xOriginTitle = 0;
4168     im->xsize = 400;
4169     im->ygridstep = DNAN;
4170     im->yimg = 0;
4171     im->ylegend[0] = '\0';
4172     im->second_axis_scale = 0; /* 0 disables it */
4173     im->second_axis_shift = 0; /* no shift by default */
4174     im->second_axis_legend[0] = '\0';
4175     im->second_axis_format[0] = '\0';
4176     im->yorigin = 0;
4177     im->yOriginLegend = 0;
4178     im->yOriginLegendY = 0;
4179     im->yOriginLegendY2 = 0;
4180     im->yOriginTitle = 0;
4181     im->ysize = 100;
4182     im->zoom = 1;
4183
4184     im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4185     im->cr = cairo_create(im->surface);
4186
4187     for (i = 0; i < DIM(text_prop); i++) {
4188         im->text_prop[i].size = -1;
4189         im->text_prop[i].font_desc = NULL;
4190         rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4191     }
4192
4193     if (fontmap == NULL){
4194         fontmap = pango_cairo_font_map_get_default();
4195     }
4196
4197     context =  pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4198
4199     pango_cairo_context_set_resolution(context, 100);
4200
4201     pango_cairo_update_context(im->cr,context);
4202
4203     im->layout = pango_layout_new(context);
4204     g_object_unref (context);
4205
4206 //  im->layout = pango_cairo_create_layout(im->cr);
4207
4208
4209     cairo_font_options_set_hint_style
4210         (im->font_options, CAIRO_HINT_STYLE_FULL);
4211     cairo_font_options_set_hint_metrics
4212         (im->font_options, CAIRO_HINT_METRICS_ON);
4213     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4214
4215
4216
4217     for (i = 0; i < DIM(graph_col); i++)
4218         im->graph_col[i] = graph_col[i];
4219
4220
4221 }
4222
4223
4224 void rrd_graph_options(
4225     int argc,
4226     char *argv[],
4227     image_desc_t
4228     *im)
4229 {
4230     int       stroff;
4231     char     *parsetime_error = NULL;
4232     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4233     time_t    start_tmp = 0, end_tmp = 0;
4234     long      long_tmp;
4235     rrd_time_value_t start_tv, end_tv;
4236     long unsigned int color;
4237
4238     /* defines for long options without a short equivalent. should be bytes,
4239        and may not collide with (the ASCII value of) short options */
4240 #define LONGOPT_UNITS_SI 255
4241
4242 /* *INDENT-OFF* */
4243     struct option long_options[] = {
4244         { "alt-autoscale",      no_argument,       0, 'A'},
4245         { "imgformat",          required_argument, 0, 'a'},
4246         { "font-smoothing-threshold", required_argument, 0, 'B'},
4247         { "base",               required_argument, 0, 'b'},
4248         { "color",              required_argument, 0, 'c'},
4249         { "full-size-mode",     no_argument,       0, 'D'},
4250         { "daemon",             required_argument, 0, 'd'},
4251         { "slope-mode",         no_argument,       0, 'E'},
4252         { "end",                required_argument, 0, 'e'},
4253         { "force-rules-legend", no_argument,       0, 'F'},
4254         { "imginfo",            required_argument, 0, 'f'},
4255         { "graph-render-mode",  required_argument, 0, 'G'},
4256         { "no-legend",          no_argument,       0, 'g'},
4257         { "height",             required_argument, 0, 'h'},
4258         { "no-minor",           no_argument,       0, 'I'},
4259         { "interlaced",         no_argument,       0, 'i'},
4260         { "alt-autoscale-min",  no_argument,       0, 'J'},
4261         { "only-graph",         no_argument,       0, 'j'},
4262         { "units-length",       required_argument, 0, 'L'},
4263         { "lower-limit",        required_argument, 0, 'l'},
4264         { "alt-autoscale-max",  no_argument,       0, 'M'},
4265         { "zoom",               required_argument, 0, 'm'},
4266         { "no-gridfit",         no_argument,       0, 'N'},
4267         { "font",               required_argument, 0, 'n'},
4268         { "logarithmic",        no_argument,       0, 'o'},
4269         { "pango-markup",       no_argument,       0, 'P'},
4270         { "font-render-mode",   required_argument, 0, 'R'},
4271         { "rigid",              no_argument,       0, 'r'},
4272         { "step",               required_argument, 0, 'S'},
4273         { "start",              required_argument, 0, 's'},
4274         { "tabwidth",           required_argument, 0, 'T'},
4275         { "title",              required_argument, 0, 't'},
4276         { "upper-limit",        required_argument, 0, 'u'},
4277         { "vertical-label",     required_argument, 0, 'v'},
4278         { "watermark",          required_argument, 0, 'W'},
4279         { "width",              required_argument, 0, 'w'},
4280         { "units-exponent",     required_argument, 0, 'X'},
4281         { "x-grid",             required_argument, 0, 'x'},
4282         { "alt-y-grid",         no_argument,       0, 'Y'},
4283         { "y-grid",             required_argument, 0, 'y'},
4284         { "lazy",               no_argument,       0, 'z'},
4285         { "units",              required_argument, 0, LONGOPT_UNITS_SI},
4286         { "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 */
4287         { "disable-rrdtool-tag",no_argument,       0, 1001},
4288         { "right-axis",         required_argument, 0, 1002},
4289         { "right-axis-label",   required_argument, 0, 1003},
4290         { "right-axis-format",  required_argument, 0, 1004},
4291         { "legend-position",    required_argument, 0, 1005},
4292         { "legend-direction",   required_argument, 0, 1006},
4293         { "border",             required_argument, 0, 1007},
4294         { "grid-dash",          required_argument, 0, 1008},
4295         { "dynamic-labels",     no_argument,       0, 1009},
4296         {  0, 0, 0, 0}
4297 };
4298 /* *INDENT-ON* */
4299
4300     optind = 0;
4301     opterr = 0;         /* initialize getopt */
4302     rrd_parsetime("end-24h", &start_tv);
4303     rrd_parsetime("now", &end_tv);
4304     while (1) {
4305         int       option_index = 0;
4306         int       opt;
4307         int       col_start, col_end;
4308
4309         opt = getopt_long(argc, argv,
4310                           "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",
4311                           long_options, &option_index);
4312         if (opt == EOF)
4313             break;
4314         switch (opt) {
4315         case 'I':
4316             im->extra_flags |= NOMINOR;
4317             break;
4318         case 'Y':
4319             im->extra_flags |= ALTYGRID;
4320             break;
4321         case 'A':
4322             im->extra_flags |= ALTAUTOSCALE;
4323             break;
4324         case 'J':
4325             im->extra_flags |= ALTAUTOSCALE_MIN;
4326             break;
4327         case 'M':
4328             im->extra_flags |= ALTAUTOSCALE_MAX;
4329             break;
4330         case 'j':
4331             im->extra_flags |= ONLY_GRAPH;
4332             break;
4333         case 'g':
4334             im->extra_flags |= NOLEGEND;
4335             break;
4336         case 1005:
4337             if (strcmp(optarg, "north") == 0) {
4338                 im->legendposition = NORTH;
4339             } else if (strcmp(optarg, "west") == 0) {
4340                 im->legendposition = WEST;
4341             } else if (strcmp(optarg, "south") == 0) {
4342                 im->legendposition = SOUTH;
4343             } else if (strcmp(optarg, "east") == 0) {
4344                 im->legendposition = EAST;
4345             } else {
4346                 rrd_set_error("unknown legend-position '%s'", optarg);
4347                 return;
4348             }
4349             break;
4350         case 1006:
4351             if (strcmp(optarg, "topdown") == 0) {
4352                 im->legenddirection = TOP_DOWN;
4353             } else if (strcmp(optarg, "bottomup") == 0) {
4354                 im->legenddirection = BOTTOM_UP;
4355             } else {
4356                 rrd_set_error("unknown legend-position '%s'", optarg);
4357                 return;
4358             }
4359             break;
4360         case 'F':
4361             im->extra_flags |= FORCE_RULES_LEGEND;
4362             break;
4363         case 1001:
4364             im->extra_flags |= NO_RRDTOOL_TAG;
4365             break;
4366         case LONGOPT_UNITS_SI:
4367             if (im->extra_flags & FORCE_UNITS) {
4368                 rrd_set_error("--units can only be used once!");
4369                 return;
4370             }
4371             if (strcmp(optarg, "si") == 0)
4372                 im->extra_flags |= FORCE_UNITS_SI;
4373             else {
4374                 rrd_set_error("invalid argument for --units: %s", optarg);
4375                 return;
4376             }
4377             break;
4378         case 'X':
4379             im->unitsexponent = atoi(optarg);
4380             break;
4381         case 'L':
4382             im->unitslength = atoi(optarg);
4383             im->forceleftspace = 1;
4384             break;
4385         case 'T':
4386             im->tabwidth = atof(optarg);
4387             break;
4388         case 'S':
4389             im->step = atoi(optarg);
4390             break;
4391         case 'N':
4392             im->gridfit = 0;
4393             break;
4394         case 'P':
4395             im->with_markup = 1;
4396             break;
4397         case 's':
4398             if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4399                 rrd_set_error("start time: %s", parsetime_error);
4400                 return;
4401             }
4402             break;
4403         case 'e':
4404             if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4405                 rrd_set_error("end time: %s", parsetime_error);
4406                 return;
4407             }
4408             break;
4409         case 'x':
4410             if (strcmp(optarg, "none") == 0) {
4411                 im->draw_x_grid = 0;
4412                 break;
4413             };
4414             if (sscanf(optarg,
4415                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4416                        scan_gtm,
4417                        &im->xlab_user.gridst,
4418                        scan_mtm,
4419                        &im->xlab_user.mgridst,
4420                        scan_ltm,
4421                        &im->xlab_user.labst,
4422                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4423                 strncpy(im->xlab_form, optarg + stroff,
4424                         sizeof(im->xlab_form) - 1);
4425                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4426                 if ((int)
4427                     (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4428                     rrd_set_error("unknown keyword %s", scan_gtm);
4429                     return;
4430                 } else if ((int)
4431                            (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4432                            == -1) {
4433                     rrd_set_error("unknown keyword %s", scan_mtm);
4434                     return;
4435                 } else if ((int)
4436                            (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4437                     rrd_set_error("unknown keyword %s", scan_ltm);
4438                     return;
4439                 }
4440                 im->xlab_user.minsec = 1;
4441                 im->xlab_user.stst = im->xlab_form;
4442             } else {
4443                 rrd_set_error("invalid x-grid format");
4444                 return;
4445             }
4446             break;
4447         case 'y':
4448
4449             if (strcmp(optarg, "none") == 0) {
4450                 im->draw_y_grid = 0;
4451                 break;
4452             };
4453             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4454                 if (im->ygridstep <= 0) {
4455                     rrd_set_error("grid step must be > 0");
4456                     return;
4457                 } else if (im->ylabfact < 1) {
4458                     rrd_set_error("label factor must be > 0");
4459                     return;
4460                 }
4461             } else {
4462                 rrd_set_error("invalid y-grid format");
4463                 return;
4464             }
4465             break;
4466         case 1007:
4467             im->draw_3d_border = atoi(optarg);
4468             break;
4469         case 1008: /* grid-dash */
4470             if(sscanf(optarg,
4471                       "%lf:%lf",
4472                       &im->grid_dash_on,
4473                       &im->grid_dash_off) != 2) {
4474                 rrd_set_error("expected grid-dash format float:float");
4475                 return;
4476             }
4477             break;   
4478         case 1009: /* enable dynamic labels */
4479             im->dynamic_labels = 1;
4480             break;         
4481         case 1002: /* right y axis */
4482
4483             if(sscanf(optarg,
4484                       "%lf:%lf",
4485                       &im->second_axis_scale,
4486                       &im->second_axis_shift) == 2) {
4487                 if(im->second_axis_scale==0){
4488                     rrd_set_error("the second_axis_scale  must not be 0");
4489                     return;
4490                 }
4491             } else {
4492                 rrd_set_error("invalid right-axis format expected scale:shift");
4493                 return;
4494             }
4495             break;
4496         case 1003:
4497             strncpy(im->second_axis_legend,optarg,150);
4498             im->second_axis_legend[150]='\0';
4499             break;
4500         case 1004:
4501             if (bad_format(optarg)){
4502                 rrd_set_error("use either %le or %lf formats");
4503                 return;
4504             }
4505             strncpy(im->second_axis_format,optarg,150);
4506             im->second_axis_format[150]='\0';
4507             break;
4508         case 'v':
4509             strncpy(im->ylegend, optarg, 150);
4510             im->ylegend[150] = '\0';
4511             break;
4512         case 'u':
4513             im->maxval = atof(optarg);
4514             break;
4515         case 'l':
4516             im->minval = atof(optarg);
4517             break;
4518         case 'b':
4519             im->base = atol(optarg);
4520             if (im->base != 1024 && im->base != 1000) {
4521                 rrd_set_error
4522                     ("the only sensible value for base apart from 1000 is 1024");
4523                 return;
4524             }
4525             break;
4526         case 'w':
4527             long_tmp = atol(optarg);
4528             if (long_tmp < 10) {
4529                 rrd_set_error("width below 10 pixels");
4530                 return;
4531             }
4532             im->xsize = long_tmp;
4533             break;
4534         case 'h':
4535             long_tmp = atol(optarg);
4536             if (long_tmp < 10) {
4537                 rrd_set_error("height below 10 pixels");
4538                 return;
4539             }
4540             im->ysize = long_tmp;
4541             break;
4542         case 'D':
4543             im->extra_flags |= FULL_SIZE_MODE;
4544             break;
4545         case 'i':
4546             /* interlaced png not supported at the moment */
4547             break;
4548         case 'r':
4549             im->rigid = 1;
4550             break;
4551         case 'f':
4552             im->imginfo = optarg;
4553             break;
4554         case 'a':
4555             if ((int)
4556                 (im->imgformat = if_conv(optarg)) == -1) {
4557                 rrd_set_error("unsupported graphics format '%s'", optarg);
4558                 return;
4559             }
4560             break;
4561         case 'z':
4562             im->lazy = 1;
4563             break;
4564         case 'E':
4565             im->slopemode = 1;
4566             break;
4567         case 'o':
4568             im->logarithmic = 1;
4569             break;
4570         case 'c':
4571             if (sscanf(optarg,
4572                        "%10[A-Z]#%n%8lx%n",
4573                        col_nam, &col_start, &color, &col_end) == 2) {
4574                 int       ci;
4575                 int       col_len = col_end - col_start;
4576
4577                 switch (col_len) {
4578                 case 3:
4579                     color =
4580                         (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4581                                                          0x011000) |
4582                          ((color & 0x00F)
4583                           * 0x001100)
4584                          | 0x000000FF);
4585                     break;
4586                 case 4:
4587                     color =
4588                         (((color & 0xF000) *
4589                           0x11000) | ((color & 0x0F00) *
4590                                       0x01100) | ((color &
4591                                                    0x00F0) *
4592                                                   0x00110) |
4593                          ((color & 0x000F) * 0x00011)
4594                         );
4595                     break;
4596                 case 6:
4597                     color = (color << 8) + 0xff /* shift left by 8 */ ;
4598                     break;
4599                 case 8:
4600                     break;
4601                 default:
4602                     rrd_set_error("the color format is #RRGGBB[AA]");
4603                     return;
4604                 }
4605                 if ((ci = grc_conv(col_nam)) != -1) {
4606                     im->graph_col[ci] = gfx_hex_to_col(color);
4607                 } else {
4608                     rrd_set_error("invalid color name '%s'", col_nam);
4609                     return;
4610                 }
4611             } else {
4612                 rrd_set_error("invalid color def format");
4613                 return;
4614             }
4615             break;
4616         case 'n':{
4617             char      prop[15];
4618             double    size = 1;
4619             int       end;
4620
4621             if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4622                 int       sindex, propidx;
4623
4624                 if ((sindex = text_prop_conv(prop)) != -1) {
4625                     for (propidx = sindex;
4626                          propidx < TEXT_PROP_LAST; propidx++) {
4627                         if (size > 0) {
4628                             rrd_set_font_desc(im,propidx,NULL,size);
4629                         }
4630                         if ((int) strlen(optarg) > end+2) {
4631                             if (optarg[end] == ':') {
4632                                 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4633                             } else {
4634                                 rrd_set_error
4635                                     ("expected : after font size in '%s'",
4636                                      optarg);
4637                                 return;
4638                             }
4639                         }
4640                         /* only run the for loop for DEFAULT (0) for
4641                            all others, we break here. woodo programming */
4642                         if (propidx == sindex && sindex != 0)
4643                             break;
4644                     }
4645                 } else {
4646                     rrd_set_error("invalid fonttag '%s'", prop);
4647                     return;
4648                 }
4649             } else {
4650                 rrd_set_error("invalid text property format");
4651                 return;
4652             }
4653             break;
4654         }
4655         case 'm':
4656             im->zoom = atof(optarg);
4657             if (im->zoom <= 0.0) {
4658                 rrd_set_error("zoom factor must be > 0");
4659                 return;
4660             }
4661             break;
4662         case 't':
4663             strncpy(im->title, optarg, 150);
4664             im->title[150] = '\0';
4665             break;
4666         case 'R':
4667             if (strcmp(optarg, "normal") == 0) {
4668                 cairo_font_options_set_antialias
4669                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4670                 cairo_font_options_set_hint_style
4671                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4672             } else if (strcmp(optarg, "light") == 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_SLIGHT);
4677             } else if (strcmp(optarg, "mono") == 0) {
4678                 cairo_font_options_set_antialias
4679                     (im->font_options, CAIRO_ANTIALIAS_NONE);
4680                 cairo_font_options_set_hint_style
4681                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4682             } else {
4683                 rrd_set_error("unknown font-render-mode '%s'", optarg);
4684                 return;
4685             }
4686             break;
4687         case 'G':
4688             if (strcmp(optarg, "normal") == 0)
4689                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4690             else if (strcmp(optarg, "mono") == 0)
4691                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4692             else {
4693                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4694                 return;
4695             }
4696             break;
4697         case 'B':
4698             /* not supported curently */
4699             break;
4700         case 'W':
4701             strncpy(im->watermark, optarg, 100);
4702             im->watermark[99] = '\0';
4703             break;
4704         case 'd':
4705         {
4706             if (im->daemon_addr != NULL)
4707             {
4708                 rrd_set_error ("You cannot specify --daemon "
4709                         "more than once.");
4710                 return;
4711             }
4712
4713             im->daemon_addr = strdup(optarg);
4714             if (im->daemon_addr == NULL)
4715             {
4716               rrd_set_error("strdup failed");
4717               return;
4718             }
4719
4720             break;
4721         }
4722         case '?':
4723             if (optopt != 0)
4724                 rrd_set_error("unknown option '%c'", optopt);
4725             else
4726                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4727             return;
4728         }
4729     } /* while (1) */
4730
4731     pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4732     pango_layout_context_changed(im->layout);
4733
4734
4735
4736     if (im->logarithmic && im->minval <= 0) {
4737         rrd_set_error
4738             ("for a logarithmic yaxis you must specify a lower-limit > 0");
4739         return;
4740     }
4741
4742     if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4743         /* error string is set in rrd_parsetime.c */
4744         return;
4745     }
4746
4747     if (start_tmp < 3600 * 24 * 365 * 10) {
4748         rrd_set_error
4749             ("the first entry to fetch should be after 1980 (%ld)",
4750              start_tmp);
4751         return;
4752     }
4753
4754     if (end_tmp < start_tmp) {
4755         rrd_set_error
4756             ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4757         return;
4758     }
4759
4760     im->start = start_tmp;
4761     im->end = end_tmp;
4762     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4763 }
4764
4765 int rrd_graph_color(
4766     image_desc_t
4767     *im,
4768     char *var,
4769     char *err,
4770     int optional)
4771 {
4772     char     *color;
4773     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4774
4775     color = strstr(var, "#");
4776     if (color == NULL) {
4777         if (optional == 0) {
4778             rrd_set_error("Found no color in %s", err);
4779             return 0;
4780         }
4781         return 0;
4782     } else {
4783         int       n = 0;
4784         char     *rest;
4785         long unsigned int col;
4786
4787         rest = strstr(color, ":");
4788         if (rest != NULL)
4789             n = rest - color;
4790         else
4791             n = strlen(color);
4792         switch (n) {
4793         case 7:
4794             sscanf(color, "#%6lx%n", &col, &n);
4795             col = (col << 8) + 0xff /* shift left by 8 */ ;
4796             if (n != 7)
4797                 rrd_set_error("Color problem in %s", err);
4798             break;
4799         case 9:
4800             sscanf(color, "#%8lx%n", &col, &n);
4801             if (n == 9)
4802                 break;
4803         default:
4804             rrd_set_error("Color problem in %s", err);
4805         }
4806         if (rrd_test_error())
4807             return 0;
4808         gdp->col = gfx_hex_to_col(col);
4809         return n;
4810     }
4811 }
4812
4813
4814 int bad_format(
4815     char *fmt)
4816 {
4817     char     *ptr;
4818     int       n = 0;
4819
4820     ptr = fmt;
4821     while (*ptr != '\0')
4822         if (*ptr++ == '%') {
4823
4824             /* line cannot end with percent char */
4825             if (*ptr == '\0')
4826                 return 1;
4827             /* '%s', '%S' and '%%' are allowed */
4828             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4829                 ptr++;
4830             /* %c is allowed (but use only with vdef!) */
4831             else if (*ptr == 'c') {
4832                 ptr++;
4833                 n = 1;
4834             }
4835
4836             /* or else '% 6.2lf' and such are allowed */
4837             else {
4838                 /* optional padding character */
4839                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4840                     ptr++;
4841                 /* This should take care of 'm.n' with all three optional */
4842                 while (*ptr >= '0' && *ptr <= '9')
4843                     ptr++;
4844                 if (*ptr == '.')
4845                     ptr++;
4846                 while (*ptr >= '0' && *ptr <= '9')
4847                     ptr++;
4848                 /* Either 'le', 'lf' or 'lg' must follow here */
4849                 if (*ptr++ != 'l')
4850                     return 1;
4851                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4852                     ptr++;
4853                 else
4854                     return 1;
4855                 n++;
4856             }
4857         }
4858
4859     return (n != 1);
4860 }
4861
4862
4863 int vdef_parse(
4864     struct graph_desc_t
4865     *gdes,
4866     const char *const str)
4867 {
4868     /* A VDEF currently is either "func" or "param,func"
4869      * so the parsing is rather simple.  Change if needed.
4870      */
4871     double    param;
4872     char      func[30];
4873     int       n;
4874
4875     n = 0;
4876     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4877     if (n == (int) strlen(str)) {   /* matched */
4878         ;
4879     } else {
4880         n = 0;
4881         sscanf(str, "%29[A-Z]%n", func, &n);
4882         if (n == (int) strlen(str)) {   /* matched */
4883             param = DNAN;
4884         } else {
4885             rrd_set_error
4886                 ("Unknown function string '%s' in VDEF '%s'",
4887                  str, gdes->vname);
4888             return -1;
4889         }
4890     }
4891     if (!strcmp("PERCENT", func))
4892         gdes->vf.op = VDEF_PERCENT;
4893     else if (!strcmp("PERCENTNAN", func))
4894         gdes->vf.op = VDEF_PERCENTNAN;
4895     else if (!strcmp("MAXIMUM", func))
4896         gdes->vf.op = VDEF_MAXIMUM;
4897     else if (!strcmp("AVERAGE", func))
4898         gdes->vf.op = VDEF_AVERAGE;
4899     else if (!strcmp("STDEV", func))
4900         gdes->vf.op = VDEF_STDEV;
4901     else if (!strcmp("MINIMUM", func))
4902         gdes->vf.op = VDEF_MINIMUM;
4903     else if (!strcmp("TOTAL", func))
4904         gdes->vf.op = VDEF_TOTAL;
4905     else if (!strcmp("FIRST", func))
4906         gdes->vf.op = VDEF_FIRST;
4907     else if (!strcmp("LAST", func))
4908         gdes->vf.op = VDEF_LAST;
4909     else if (!strcmp("LSLSLOPE", func))
4910         gdes->vf.op = VDEF_LSLSLOPE;
4911     else if (!strcmp("LSLINT", func))
4912         gdes->vf.op = VDEF_LSLINT;
4913     else if (!strcmp("LSLCORREL", func))
4914         gdes->vf.op = VDEF_LSLCORREL;
4915     else {
4916         rrd_set_error
4917             ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4918         return -1;
4919     };
4920     switch (gdes->vf.op) {
4921     case VDEF_PERCENT:
4922     case VDEF_PERCENTNAN:
4923         if (isnan(param)) { /* no parameter given */
4924             rrd_set_error
4925                 ("Function '%s' needs parameter in VDEF '%s'\n",
4926                  func, gdes->vname);
4927             return -1;
4928         };
4929         if (param >= 0.0 && param <= 100.0) {
4930             gdes->vf.param = param;
4931             gdes->vf.val = DNAN;    /* undefined */
4932             gdes->vf.when = 0;  /* undefined */
4933             gdes->vf.never = 1;
4934         } else {
4935             rrd_set_error
4936                 ("Parameter '%f' out of range in VDEF '%s'\n",
4937                  param, gdes->vname);
4938             return -1;
4939         };
4940         break;
4941     case VDEF_MAXIMUM:
4942     case VDEF_AVERAGE:
4943     case VDEF_STDEV:
4944     case VDEF_MINIMUM:
4945     case VDEF_TOTAL:
4946     case VDEF_FIRST:
4947     case VDEF_LAST:
4948     case VDEF_LSLSLOPE:
4949     case VDEF_LSLINT:
4950     case VDEF_LSLCORREL:
4951         if (isnan(param)) {
4952             gdes->vf.param = DNAN;
4953             gdes->vf.val = DNAN;
4954             gdes->vf.when = 0;
4955             gdes->vf.never = 1;
4956         } else {
4957             rrd_set_error
4958                 ("Function '%s' needs no parameter in VDEF '%s'\n",
4959                  func, gdes->vname);
4960             return -1;
4961         };
4962         break;
4963     };
4964     return 0;
4965 }
4966
4967
4968 int vdef_calc(
4969     image_desc_t *im,
4970     int gdi)
4971 {
4972     graph_desc_t *src, *dst;
4973     rrd_value_t *data;
4974     long      step, steps;
4975
4976     dst = &im->gdes[gdi];
4977     src = &im->gdes[dst->vidx];
4978     data = src->data + src->ds;
4979
4980     steps = (src->end - src->start) / src->step;
4981 #if 0
4982     printf
4983         ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4984          src->start, src->end, steps);
4985 #endif
4986     switch (dst->vf.op) {
4987     case VDEF_PERCENT:{
4988         rrd_value_t *array;
4989         int       field;
4990         if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4991             rrd_set_error("malloc VDEV_PERCENT");
4992             return -1;
4993         }
4994         for (step = 0; step < steps; step++) {
4995             array[step] = data[step * src->ds_cnt];
4996         }
4997         qsort(array, step, sizeof(double), vdef_percent_compar);
4998         field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
4999         dst->vf.val = array[field];
5000         dst->vf.when = 0;   /* no time component */
5001         dst->vf.never = 1;
5002         free(array);
5003 #if 0
5004         for (step = 0; step < steps; step++)
5005             printf("DEBUG: %3li:%10.2f %c\n",
5006                    step, array[step], step == field ? '*' : ' ');
5007 #endif
5008     }
5009         break;
5010     case VDEF_PERCENTNAN:{
5011         rrd_value_t *array;
5012         int       field;
5013        /* count number of "valid" values */
5014        int nancount=0;
5015        for (step = 0; step < steps; step++) {
5016          if (!isnan(data[step * src->ds_cnt])) { nancount++; }
5017        }
5018        /* and allocate it */
5019         if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
5020             rrd_set_error("malloc VDEV_PERCENT");
5021             return -1;
5022         }
5023        /* and fill it in */
5024        field=0;
5025         for (step = 0; step < steps; step++) {
5026            if (!isnan(data[step * src->ds_cnt])) {
5027                 array[field] = data[step * src->ds_cnt];
5028                field++;
5029             }
5030         }
5031         qsort(array, nancount, sizeof(double), vdef_percent_compar);
5032         field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
5033         dst->vf.val = array[field];
5034         dst->vf.when = 0;   /* no time component */
5035         dst->vf.never = 1;
5036         free(array);
5037     }
5038         break;
5039     case VDEF_MAXIMUM:
5040         step = 0;
5041         while (step != steps && isnan(data[step * src->ds_cnt]))
5042             step++;
5043         if (step == steps) {
5044             dst->vf.val = DNAN;
5045             dst->vf.when = 0;
5046             dst->vf.never = 1;
5047         } else {
5048             dst->vf.val = data[step * src->ds_cnt];
5049             dst->vf.when = src->start + (step + 1) * src->step;
5050             dst->vf.never = 0;
5051         }
5052         while (step != steps) {
5053             if (finite(data[step * src->ds_cnt])) {
5054                 if (data[step * src->ds_cnt] > dst->vf.val) {
5055                     dst->vf.val = data[step * src->ds_cnt];
5056                     dst->vf.when = src->start + (step + 1) * src->step;
5057                     dst->vf.never = 0;
5058                 }
5059             }
5060             step++;
5061         }
5062         break;
5063     case VDEF_TOTAL:
5064     case VDEF_STDEV:
5065     case VDEF_AVERAGE:{
5066         int       cnt = 0;
5067         double    sum = 0.0;
5068         double    average = 0.0;
5069
5070         for (step = 0; step < steps; step++) {
5071             if (finite(data[step * src->ds_cnt])) {
5072                 sum += data[step * src->ds_cnt];
5073                 cnt++;
5074             };
5075         }
5076         if (cnt) {
5077             if (dst->vf.op == VDEF_TOTAL) {
5078                 dst->vf.val = sum * src->step;
5079                 dst->vf.when = 0;   /* no time component */
5080                 dst->vf.never = 1;
5081             } else if (dst->vf.op == VDEF_AVERAGE) {
5082                 dst->vf.val = sum / cnt;
5083                 dst->vf.when = 0;   /* no time component */
5084                 dst->vf.never = 1;
5085             } else {
5086                 average = sum / cnt;
5087                 sum = 0.0;
5088                 for (step = 0; step < steps; step++) {
5089                     if (finite(data[step * src->ds_cnt])) {
5090                         sum += pow((data[step * src->ds_cnt] - average), 2.0);
5091                     };
5092                 }
5093                 dst->vf.val = pow(sum / cnt, 0.5);
5094                 dst->vf.when = 0;   /* no time component */
5095                 dst->vf.never = 1;
5096             };
5097         } else {
5098             dst->vf.val = DNAN;
5099             dst->vf.when = 0;
5100             dst->vf.never = 1;
5101         }
5102     }
5103         break;
5104     case VDEF_MINIMUM:
5105         step = 0;
5106         while (step != steps && isnan(data[step * src->ds_cnt]))
5107             step++;
5108         if (step == steps) {
5109             dst->vf.val = DNAN;
5110             dst->vf.when = 0;
5111             dst->vf.never = 1;
5112         } else {
5113             dst->vf.val = data[step * src->ds_cnt];
5114             dst->vf.when = src->start + (step + 1) * src->step;
5115             dst->vf.never = 0;
5116         }
5117         while (step != steps) {
5118             if (finite(data[step * src->ds_cnt])) {
5119                 if (data[step * src->ds_cnt] < dst->vf.val) {
5120                     dst->vf.val = data[step * src->ds_cnt];
5121                     dst->vf.when = src->start + (step + 1) * src->step;
5122                     dst->vf.never = 0;
5123                 }
5124             }
5125             step++;
5126         }
5127         break;
5128     case VDEF_FIRST:
5129         /* The time value returned here is one step before the
5130          * actual time value.  This is the start of the first
5131          * non-NaN interval.
5132          */
5133         step = 0;
5134         while (step != steps && isnan(data[step * src->ds_cnt]))
5135             step++;
5136         if (step == steps) {    /* all entries were NaN */
5137             dst->vf.val = DNAN;
5138             dst->vf.when = 0;
5139             dst->vf.never = 1;
5140         } else {
5141             dst->vf.val = data[step * src->ds_cnt];
5142             dst->vf.when = src->start + step * src->step;
5143             dst->vf.never = 0;
5144         }
5145         break;
5146     case VDEF_LAST:
5147         /* The time value returned here is the
5148          * actual time value.  This is the end of the last
5149          * non-NaN interval.
5150          */
5151         step = steps - 1;
5152         while (step >= 0 && isnan(data[step * src->ds_cnt]))
5153             step--;
5154         if (step < 0) { /* all entries were NaN */
5155             dst->vf.val = DNAN;
5156             dst->vf.when = 0;
5157             dst->vf.never = 1;
5158         } else {
5159             dst->vf.val = data[step * src->ds_cnt];
5160             dst->vf.when = src->start + (step + 1) * src->step;
5161             dst->vf.never = 0;
5162         }
5163         break;
5164     case VDEF_LSLSLOPE:
5165     case VDEF_LSLINT:
5166     case VDEF_LSLCORREL:{
5167         /* Bestfit line by linear least squares method */
5168
5169         int       cnt = 0;
5170         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5171
5172         SUMx = 0;
5173         SUMy = 0;
5174         SUMxy = 0;
5175         SUMxx = 0;
5176         SUMyy = 0;
5177         for (step = 0; step < steps; step++) {
5178             if (finite(data[step * src->ds_cnt])) {
5179                 cnt++;
5180                 SUMx += step;
5181                 SUMxx += step * step;
5182                 SUMxy += step * data[step * src->ds_cnt];
5183                 SUMy += data[step * src->ds_cnt];
5184                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5185             };
5186         }
5187
5188         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5189         y_intercept = (SUMy - slope * SUMx) / cnt;
5190         correl =
5191             (SUMxy -
5192              (SUMx * SUMy) / cnt) /
5193             sqrt((SUMxx -
5194                   (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5195         if (cnt) {
5196             if (dst->vf.op == VDEF_LSLSLOPE) {
5197                 dst->vf.val = slope;
5198                 dst->vf.when = 0;
5199                 dst->vf.never = 1;
5200             } else if (dst->vf.op == VDEF_LSLINT) {
5201                 dst->vf.val = y_intercept;
5202                 dst->vf.when = 0;
5203                 dst->vf.never = 1;
5204             } else if (dst->vf.op == VDEF_LSLCORREL) {
5205                 dst->vf.val = correl;
5206                 dst->vf.when = 0;
5207                 dst->vf.never = 1;
5208             };
5209         } else {
5210             dst->vf.val = DNAN;
5211             dst->vf.when = 0;
5212             dst->vf.never = 1;
5213         }
5214     }
5215         break;
5216     }
5217     return 0;
5218 }
5219
5220 /* NaN < -INF < finite_values < INF */
5221 int vdef_percent_compar(
5222     const void
5223     *a,
5224     const void
5225     *b)
5226 {
5227     /* Equality is not returned; this doesn't hurt except
5228      * (maybe) for a little performance.
5229      */
5230
5231     /* First catch NaN values. They are smallest */
5232     if (isnan(*(double *) a))
5233         return -1;
5234     if (isnan(*(double *) b))
5235         return 1;
5236     /* NaN doesn't reach this part so INF and -INF are extremes.
5237      * The sign from isinf() is compatible with the sign we return
5238      */
5239     if (isinf(*(double *) a))
5240         return isinf(*(double *) a);
5241     if (isinf(*(double *) b))
5242         return isinf(*(double *) b);
5243     /* If we reach this, both values must be finite */
5244     if (*(double *) a < *(double *) b)
5245         return -1;
5246     else
5247         return 1;
5248 }
5249
5250 void grinfo_push(
5251     image_desc_t *im,
5252     char *key,
5253     rrd_info_type_t type,
5254     rrd_infoval_t value)
5255 {
5256     im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5257     if (im->grinfo == NULL) {
5258         im->grinfo = im->grinfo_current;
5259     }
5260 }
5261
5262
5263 void time_clean(
5264     char *result,
5265     char *format)
5266 {
5267     int       j, jj;
5268     
5269 /*     Handling based on
5270        - ANSI C99 Specifications                         http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
5271        - Single UNIX Specification version 2             http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html 
5272        - POSIX:2001/Single UNIX Specification version 3  http://www.opengroup.org/onlinepubs/009695399/functions/strftime.html
5273        - POSIX:2008 Specifications                       http://www.opengroup.org/onlinepubs/9699919799/functions/strftime.html
5274        Specifications tells 
5275        "If a conversion specifier is not one of the above, the behavior is undefined."
5276
5277       C99 tells
5278        "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.
5279
5280       POSIX:2001 tells
5281       "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."
5282
5283       POSIX:2008 introduce more complexe behavior that are not handled here.
5284
5285       According to this, this code will replace:
5286       - % followed by @ by a %@
5287       - % followed by   by a %SPACE
5288       - % followed by . by a %.
5289       - % followed by % by a %
5290       - % followed by t by a TAB
5291       - % followed by E then anything by '-'
5292       - % followed by O then anything by '-'
5293       - % followed by anything else by at least one '-'. More characters may be added to better fit expected output length
5294 */
5295
5296     jj = 0;
5297     for(j = 0; (j < FMT_LEG_LEN - 1) && (jj < FMT_LEG_LEN); j++) { /* we don't need to parse the last char */
5298         if (format[j] == '%') {
5299             if ((format[j+1] == 'E') || (format[j+1] == 'O')) {
5300                 result[jj++] = '-';
5301                 j+=2; /* We skip next 2 following char */
5302             } else if ((format[j+1] == 'C') || (format[j+1] == 'd') ||
5303                        (format[j+1] == 'g') || (format[j+1] == 'H') ||
5304                        (format[j+1] == 'I') || (format[j+1] == 'm') ||
5305                        (format[j+1] == 'M') || (format[j+1] == 'S') ||
5306                        (format[j+1] == 'U') || (format[j+1] == 'V') ||
5307                        (format[j+1] == 'W') || (format[j+1] == 'y')) {
5308                 result[jj++] = '-';
5309                 if (jj < FMT_LEG_LEN) {
5310                     result[jj++] = '-';
5311                 }
5312                 j++; /* We skip the following char */
5313             } else if (format[j+1] == 'j') {
5314                 result[jj++] = '-';
5315                 if (jj < FMT_LEG_LEN - 1) {
5316                     result[jj++] = '-';
5317                     result[jj++] = '-';
5318                }
5319                 j++; /* We skip the following char */
5320             } else if ((format[j+1] == 'G') || (format[j+1] == 'Y')) {
5321                 /* Assuming Year on 4 digit */
5322                 result[jj++] = '-';
5323                 if (jj < FMT_LEG_LEN - 2) {
5324                     result[jj++] = '-';
5325                     result[jj++] = '-';
5326                     result[jj++] = '-';
5327                 }
5328                 j++; /* We skip the following char */
5329             } else if (format[j+1] == 'R') {
5330                 result[jj++] = '-';
5331                 if (jj < FMT_LEG_LEN - 3) {
5332                     result[jj++] = '-';
5333                     result[jj++] = ':';
5334                     result[jj++] = '-';
5335                     result[jj++] = '-';
5336                 }
5337                 j++; /* We skip the following char */
5338             } else if (format[j+1] == 'T') {
5339                 result[jj++] = '-';
5340                 if (jj < FMT_LEG_LEN - 6) {
5341                     result[jj++] = '-';
5342                     result[jj++] = ':';
5343                     result[jj++] = '-';
5344                     result[jj++] = '-';
5345                     result[jj++] = ':';
5346                     result[jj++] = '-';
5347                     result[jj++] = '-';
5348                 }
5349                 j++; /* We skip the following char */
5350             } else if (format[j+1] == 'F') {
5351                 result[jj++] = '-';
5352                 if (jj < FMT_LEG_LEN - 8) {
5353                     result[jj++] = '-';
5354                     result[jj++] = '-';
5355                     result[jj++] = '-';
5356                     result[jj++] = '-';
5357                     result[jj++] = '-';
5358                     result[jj++] = '-';
5359                     result[jj++] = '-';
5360                     result[jj++] = '-';
5361                     result[jj++] = '-';
5362                 }
5363                 j++; /* We skip the following char */
5364             } else if (format[j+1] == 'D') {
5365                 result[jj++] = '-';
5366                 if (jj < FMT_LEG_LEN - 6) {
5367                     result[jj++] = '-';
5368                     result[jj++] = '/';
5369                     result[jj++] = '-';
5370                     result[jj++] = '-';
5371                     result[jj++] = '/';
5372                     result[jj++] = '-';
5373                     result[jj++] = '-';
5374                 }
5375                 j++; /* We skip the following char */
5376             } else if (format[j+1] == 'n') {
5377                 result[jj++] = '\r';
5378                 result[jj++] = '\n';
5379                 j++; /* We skip the following char */
5380             } else if (format[j+1] == 't') {
5381                 result[jj++] = '\t';
5382                 j++; /* We skip the following char */
5383             } else if (format[j+1] == '%') {
5384                 result[jj++] = '%';
5385                 j++; /* We skip the following char */
5386             } else if (format[j+1] == ' ') {
5387                 if (jj < FMT_LEG_LEN - 1) {
5388                     result[jj++] = '%';
5389                     result[jj++] = ' ';
5390                 }
5391                 j++; /* We skip the following char */
5392             } else if (format[j+1] == '.') {
5393                 if (jj < FMT_LEG_LEN - 1) {
5394                     result[jj++] = '%';
5395                     result[jj++] = '.';
5396                 }
5397                 j++; /* We skip the following char */
5398             } else if (format[j+1] == '@') {
5399                 if (jj < FMT_LEG_LEN - 1) {
5400                     result[jj++] = '%';
5401                     result[jj++] = '@';
5402                 }
5403                 j++; /* We skip the following char */
5404             } else {
5405                 result[jj++] = '-';
5406                 j++; /* We skip the following char */
5407             }
5408         } else {
5409                 result[jj++] = format[j];
5410         }
5411     }
5412     result[jj] = '\0'; /* We must force the end of the string */
5413 }