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