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