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