5081c8a9341d9c689f71c12bf43150730d8c8c66
[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
1430     switch (baseint) {
1431     case TMT_SECOND:
1432         tm.       tm_sec -= tm.tm_sec % basestep;
1433
1434         break;
1435     case TMT_MINUTE:
1436         tm.       tm_sec = 0;
1437         tm.       tm_min -= tm.tm_min % basestep;
1438
1439         break;
1440     case TMT_HOUR:
1441         tm.       tm_sec = 0;
1442         tm.       tm_min = 0;
1443         tm.       tm_hour -= tm.tm_hour % basestep;
1444
1445         break;
1446     case TMT_DAY:
1447         /* we do NOT look at the basestep for this ... */
1448         tm.       tm_sec = 0;
1449         tm.       tm_min = 0;
1450         tm.       tm_hour = 0;
1451
1452         break;
1453     case TMT_WEEK:
1454         /* we do NOT look at the basestep for this ... */
1455         tm.       tm_sec = 0;
1456         tm.       tm_min = 0;
1457         tm.       tm_hour = 0;
1458         tm.       tm_mday -= tm.tm_wday - find_first_weekday();
1459
1460         if (tm.tm_wday == 0 && find_first_weekday() > 0)
1461             tm.       tm_mday -= 7; /* we want the *previous* week */
1462
1463         break;
1464     case TMT_MONTH:
1465         tm.       tm_sec = 0;
1466         tm.       tm_min = 0;
1467         tm.       tm_hour = 0;
1468         tm.       tm_mday = 1;
1469         tm.       tm_mon -= tm.tm_mon % basestep;
1470
1471         break;
1472
1473     case TMT_YEAR:
1474         tm.       tm_sec = 0;
1475         tm.       tm_min = 0;
1476         tm.       tm_hour = 0;
1477         tm.       tm_mday = 1;
1478         tm.       tm_mon = 0;
1479         tm.       tm_year -= (
1480     tm.tm_year + 1900) %basestep;
1481
1482     }
1483     return mktime(&tm);
1484 }
1485
1486 /* identify the point where the next gridline, label ... gets placed */
1487 time_t find_next_time(
1488     time_t current,     /* what is the initial time */
1489     enum tmt_en baseint,    /* what is the basic interval */
1490     long basestep       /* how many if these do we jump a time */
1491     )
1492 {
1493     struct tm tm;
1494     time_t    madetime;
1495
1496     localtime_r(&current, &tm);
1497
1498     int limit = 2;
1499     switch (baseint) {
1500     case TMT_SECOND: limit = 7200; break;
1501     case TMT_MINUTE: limit = 120; break;
1502     case TMT_HOUR: limit = 2; break;
1503     default: limit = 2; break;
1504     }
1505     do {
1506         switch (baseint) {
1507         case TMT_SECOND:
1508             tm.       tm_sec += basestep;
1509
1510             break;
1511         case TMT_MINUTE:
1512             tm.       tm_min += basestep;
1513
1514             break;
1515         case TMT_HOUR:
1516             tm.       tm_hour += basestep;
1517
1518             break;
1519         case TMT_DAY:
1520             tm.       tm_mday += basestep;
1521
1522             break;
1523         case TMT_WEEK:
1524             tm.       tm_mday += 7 * basestep;
1525
1526             break;
1527         case TMT_MONTH:
1528             tm.       tm_mon += basestep;
1529
1530             break;
1531         case TMT_YEAR:
1532             tm.       tm_year += basestep;
1533         }
1534         madetime = mktime(&tm);
1535     } while (madetime == -1 && limit-- >= 0);   /* this is necessary to skip impossible times
1536                                    like the daylight saving time skips */
1537     return madetime;
1538
1539 }
1540
1541
1542 /* calculate values required for PRINT and GPRINT functions */
1543
1544 int print_calc(
1545     image_desc_t *im)
1546 {
1547     long      i, ii, validsteps;
1548     double    printval;
1549     struct tm tmvdef;
1550     int       graphelement = 0;
1551     long      vidx;
1552     int       max_ii;
1553     double    magfact = -1;
1554     char     *si_symb = "";
1555     char     *percent_s;
1556     int       prline_cnt = 0;
1557
1558     /* wow initializing tmvdef is quite a task :-) */
1559     time_t    now = time(NULL);
1560
1561     localtime_r(&now, &tmvdef);
1562     for (i = 0; i < im->gdes_c; i++) {
1563         vidx = im->gdes[i].vidx;
1564         switch (im->gdes[i].gf) {
1565         case GF_PRINT:
1566         case GF_GPRINT:
1567             /* PRINT and GPRINT can now print VDEF generated values.
1568              * There's no need to do any calculations on them as these
1569              * calculations were already made.
1570              */
1571             if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1572                 printval = im->gdes[vidx].vf.val;
1573                 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1574             } else {    /* need to calculate max,min,avg etcetera */
1575                 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1576                           / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1577                 printval = DNAN;
1578                 validsteps = 0;
1579                 for (ii = im->gdes[vidx].ds;
1580                      ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1581                     if (!finite(im->gdes[vidx].data[ii]))
1582                         continue;
1583                     if (isnan(printval)) {
1584                         printval = im->gdes[vidx].data[ii];
1585                         validsteps++;
1586                         continue;
1587                     }
1588
1589                     switch (im->gdes[i].cf) {
1590                     case CF_HWPREDICT:
1591                     case CF_MHWPREDICT:
1592                     case CF_DEVPREDICT:
1593                     case CF_DEVSEASONAL:
1594                     case CF_SEASONAL:
1595                     case CF_AVERAGE:
1596                         validsteps++;
1597                         printval += im->gdes[vidx].data[ii];
1598                         break;
1599                     case CF_MINIMUM:
1600                         printval = min(printval, im->gdes[vidx].data[ii]);
1601                         break;
1602                     case CF_FAILURES:
1603                     case CF_MAXIMUM:
1604                         printval = max(printval, im->gdes[vidx].data[ii]);
1605                         break;
1606                     case CF_LAST:
1607                         printval = im->gdes[vidx].data[ii];
1608                     }
1609                 }
1610                 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1611                     if (validsteps > 1) {
1612                         printval = (printval / validsteps);
1613                     }
1614                 }
1615             }           /* prepare printval */
1616
1617             if (!im->gdes[i].strftm && (percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1618                 /* Magfact is set to -1 upon entry to print_calc.  If it
1619                  * is still less than 0, then we need to run auto_scale.
1620                  * Otherwise, put the value into the correct units.  If
1621                  * the value is 0, then do not set the symbol or magnification
1622                  * so next the calculation will be performed again. */
1623                 if (magfact < 0.0) {
1624                     auto_scale(im, &printval, &si_symb, &magfact);
1625                     if (printval == 0.0)
1626                         magfact = -1.0;
1627                 } else {
1628                     printval /= magfact;
1629                 }
1630                 *(++percent_s) = 's';
1631             } else if (!im->gdes[i].strftm && strstr(im->gdes[i].format, "%s") != NULL) {
1632                 auto_scale(im, &printval, &si_symb, &magfact);
1633             }
1634
1635             if (im->gdes[i].gf == GF_PRINT) {
1636                 rrd_infoval_t prline;
1637
1638                 if (im->gdes[i].strftm) {
1639                     prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1640                     if (im->gdes[vidx].vf.never == 1) {
1641                        time_clean(prline.u_str, im->gdes[i].format);
1642                     } else {
1643                         strftime(prline.u_str,
1644                                  FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1645                     }
1646                 } else if (bad_format(im->gdes[i].format)) {
1647                     rrd_set_error
1648                         ("bad format for PRINT in '%s'", im->gdes[i].format);
1649                     return -1;
1650                 } else {
1651                     prline.u_str =
1652                         sprintf_alloc(im->gdes[i].format, printval, si_symb);
1653                 }
1654                 grinfo_push(im,
1655                             sprintf_alloc
1656                             ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1657                 free(prline.u_str);
1658             } else {
1659                 /* GF_GPRINT */
1660
1661                 if (im->gdes[i].strftm) {
1662                     if (im->gdes[vidx].vf.never == 1) {
1663                        time_clean(im->gdes[i].legend, im->gdes[i].format);
1664                     } else {
1665                         strftime(im->gdes[i].legend,
1666                                  FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1667                     }
1668                 } else {
1669                     if (bad_format(im->gdes[i].format)) {
1670                         rrd_set_error
1671                             ("bad format for GPRINT in '%s'",
1672                              im->gdes[i].format);
1673                         return -1;
1674                     }
1675 #ifdef HAVE_SNPRINTF
1676                     snprintf(im->gdes[i].legend,
1677                              FMT_LEG_LEN - 2,
1678                              im->gdes[i].format, printval, si_symb);
1679 #else
1680                     sprintf(im->gdes[i].legend,
1681                             im->gdes[i].format, printval, si_symb);
1682 #endif
1683                 }
1684                 graphelement = 1;
1685             }
1686             break;
1687         case GF_LINE:
1688         case GF_AREA:
1689                 case GF_GRAD:
1690         case GF_TICK:
1691             graphelement = 1;
1692             break;
1693         case GF_HRULE:
1694             if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1695                 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1696             };
1697             graphelement = 1;
1698             break;
1699         case GF_VRULE:
1700             if (im->gdes[i].xrule == 0) {   /* again ... the legend printer needs it */
1701                 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1702             };
1703             graphelement = 1;
1704             break;
1705         case GF_COMMENT:
1706         case GF_TEXTALIGN:
1707         case GF_DEF:
1708         case GF_CDEF:
1709         case GF_VDEF:
1710 #ifdef WITH_PIECHART
1711         case GF_PART:
1712 #endif
1713         case GF_SHIFT:
1714         case GF_XPORT:
1715             break;
1716         case GF_STACK:
1717             rrd_set_error
1718                 ("STACK should already be turned into LINE or AREA here");
1719             return -1;
1720             break;
1721         }
1722     }
1723     return graphelement;
1724 }
1725
1726
1727
1728 /* place legends with color spots */
1729 int leg_place(
1730     image_desc_t *im,
1731     int calc_width)
1732 {
1733     /* graph labels */
1734     int       interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1735     int       border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1736     int       fill = 0, fill_last;
1737     double    legendwidth; // = im->ximg - 2 * border;
1738     int       leg_c = 0;
1739     double    leg_x = border;
1740     int       leg_y = 0; //im->yimg;
1741     int       leg_y_prev = 0; // im->yimg;
1742     int       leg_cc;
1743     double    glue = 0;
1744     int       i, ii, mark = 0;
1745     char      default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1746     int      *legspace;
1747     char     *tab;
1748     char      saved_legend[FMT_LEG_LEN + 5];
1749
1750     if(calc_width){
1751         legendwidth = 0;
1752     }
1753     else{
1754         legendwidth = im->legendwidth - 2 * border;
1755     }
1756
1757
1758     if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1759         if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1760             rrd_set_error("malloc for legspace");
1761             return -1;
1762         }
1763
1764         for (i = 0; i < im->gdes_c; i++) {
1765             char      prt_fctn; /*special printfunctions */
1766             if(calc_width){
1767                 strcpy(saved_legend, im->gdes[i].legend);
1768             }
1769
1770             fill_last = fill;
1771             /* hide legends for rules which are not displayed */
1772             if (im->gdes[i].gf == GF_TEXTALIGN) {
1773                 default_txtalign = im->gdes[i].txtalign;
1774             }
1775
1776             if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1777                 if (im->gdes[i].gf == GF_HRULE
1778                     && (im->gdes[i].yrule <
1779                         im->minval || im->gdes[i].yrule > im->maxval))
1780                     im->gdes[i].legend[0] = '\0';
1781                 if (im->gdes[i].gf == GF_VRULE
1782                     && (im->gdes[i].xrule <
1783                         im->start || im->gdes[i].xrule > im->end))
1784                     im->gdes[i].legend[0] = '\0';
1785             }
1786
1787             /* turn \\t into tab */
1788             while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1789                 memmove(tab, tab + 1, strlen(tab));
1790                 tab[0] = (char) 9;
1791             }
1792
1793             leg_cc = strlen(im->gdes[i].legend);
1794             /* is there a controle code at the end of the legend string ? */
1795             if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1796                 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1797                 leg_cc -= 2;
1798                 im->gdes[i].legend[leg_cc] = '\0';
1799             } else {
1800                 prt_fctn = '\0';
1801             }
1802             /* only valid control codes */
1803             if (prt_fctn != 'l' && prt_fctn != 'n' &&   /* a synonym for l */
1804                 prt_fctn != 'r' &&
1805                 prt_fctn != 'j' &&
1806                 prt_fctn != 'c' &&
1807                 prt_fctn != 'u' &&
1808                 prt_fctn != '.' &&
1809                 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1810                 free(legspace);
1811                 rrd_set_error
1812                     ("Unknown control code at the end of '%s\\%c'",
1813                      im->gdes[i].legend, prt_fctn);
1814                 return -1;
1815             }
1816             /* \n -> \l */
1817             if (prt_fctn == 'n') {
1818                 prt_fctn = 'l';
1819             }
1820             /* \. is a null operation to allow strings ending in \x */
1821             if (prt_fctn == '.') {
1822                 prt_fctn = '\0';
1823             }
1824
1825             /* remove exess space from the end of the legend for \g */
1826             while (prt_fctn == 'g' &&
1827                    leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1828                 leg_cc--;
1829                 im->gdes[i].legend[leg_cc] = '\0';
1830             }
1831
1832             if (leg_cc != 0) {
1833
1834                 /* no interleg space if string ends in \g */
1835                 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1836                 if (fill > 0) {
1837                     fill += legspace[i];
1838                 }
1839                 fill +=
1840                     gfx_get_text_width(im,
1841                                        fill + border,
1842                                        im->
1843                                        text_prop
1844                                        [TEXT_PROP_LEGEND].
1845                                        font_desc,
1846                                        im->tabwidth, im->gdes[i].legend);
1847                 leg_c++;
1848             } else {
1849                 legspace[i] = 0;
1850             }
1851             /* who said there was a special tag ... ? */
1852             if (prt_fctn == 'g') {
1853                 prt_fctn = '\0';
1854             }
1855
1856             if (prt_fctn == '\0') {
1857                 if(calc_width && (fill > legendwidth)){
1858                     legendwidth = fill;
1859                 }
1860                 if (i == im->gdes_c - 1 || fill > legendwidth) {
1861                     /* just one legend item is left right or center */
1862                     switch (default_txtalign) {
1863                     case TXA_RIGHT:
1864                         prt_fctn = 'r';
1865                         break;
1866                     case TXA_CENTER:
1867                         prt_fctn = 'c';
1868                         break;
1869                     case TXA_JUSTIFIED:
1870                         prt_fctn = 'j';
1871                         break;
1872                     default:
1873                         prt_fctn = 'l';
1874                         break;
1875                     }
1876                 }
1877                 /* is it time to place the legends ? */
1878                 if (fill > legendwidth) {
1879                     if (leg_c > 1) {
1880                         /* go back one */
1881                         i--;
1882                         fill = fill_last;
1883                         leg_c--;
1884                     }
1885                 }
1886                 if (leg_c == 1 && prt_fctn == 'j') {
1887                     prt_fctn = 'l';
1888                 }
1889             }
1890
1891             if (prt_fctn != '\0') {
1892                 leg_x = border;
1893                 if (leg_c >= 2 && prt_fctn == 'j') {
1894                     glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1895                 } else {
1896                     glue = 0;
1897                 }
1898                 if (prt_fctn == 'c')
1899                     leg_x = border + (double)(legendwidth - fill) / 2.0;
1900                 if (prt_fctn == 'r')
1901                     leg_x = legendwidth - fill + border;
1902                 for (ii = mark; ii <= i; ii++) {
1903                     if (im->gdes[ii].legend[0] == '\0')
1904                         continue;   /* skip empty legends */
1905                     im->gdes[ii].leg_x = leg_x;
1906                     im->gdes[ii].leg_y = leg_y + border;
1907                     leg_x +=
1908                         (double)gfx_get_text_width(im, leg_x,
1909                                            im->
1910                                            text_prop
1911                                            [TEXT_PROP_LEGEND].
1912                                            font_desc,
1913                                            im->tabwidth, im->gdes[ii].legend)
1914                         +(double)legspace[ii]
1915                         + glue;
1916                 }
1917                 leg_y_prev = leg_y;
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                 strcpy(im->gdes[i].legend, saved_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                 sprintf(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                 sprintf(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                         sprintf(graph_label,
2090                                 im->ygrid_scale.labfmt,
2091                                 scaledstep * (double) i);
2092                     } else {
2093                         if (MaxY < 10) {
2094                             sprintf(graph_label, "%4.1f",
2095                                     scaledstep * (double) i);
2096                         } else {
2097                             sprintf(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                         sprintf(graph_label,
2106                                 im->ygrid_scale.labfmt,
2107                                 scaledstep * (double) i, sisym);
2108                     } else {
2109                         if (MaxY < 10) {
2110                             sprintf(graph_label, "%4.1f %c",
2111                                     scaledstep * (double) i, sisym);
2112                         } else {
2113                             sprintf(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                                 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2131                             } else {
2132                                 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2133                             }
2134                         }
2135                         else {
2136                            sprintf(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             sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2323         } else {
2324             sprintf(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                                 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2335                         }
2336                         else {
2337                                 sprintf(graph_label_right,"%3.0e", sval);
2338                         }
2339                 }
2340                 else {
2341                       sprintf(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       i;      
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     i = print_calc(im);
3327     if (i < 0)
3328         return -1;
3329
3330     /* if we want and can be lazy ... quit now */
3331     if (i == 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);
3338       break;
3339     case GTYPE_XY:
3340       return graph_paint_xy(im,lazy);
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)
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, i) == -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             break;
3431         case GF_TICK:
3432             for (ii = 0; ii < im->xsize; ii++) {
3433                 if (!isnan(im->gdes[i].p_data[ii])
3434                     && im->gdes[i].p_data[ii] != 0.0) {
3435                     if (im->gdes[i].yrule > 0) {
3436                         gfx_line(im,
3437                                  im->xorigin + ii,
3438                                  im->yorigin + 1.0,
3439                                  im->xorigin + ii,
3440                                  im->yorigin -
3441                                  im->gdes[i].yrule *
3442                                  im->ysize, 1.0, im->gdes[i].col);
3443                     } else if (im->gdes[i].yrule < 0) {
3444                         gfx_line(im,
3445                                  im->xorigin + ii,
3446                                  im->yorigin - im->ysize - 1.0,
3447                                  im->xorigin + ii,
3448                                  im->yorigin - im->ysize -
3449                                                 im->gdes[i].
3450                                                 yrule *
3451                                  im->ysize, 1.0, im->gdes[i].col);
3452                     }
3453                 }
3454             }
3455             break;
3456         case GF_LINE:
3457         case GF_AREA:
3458         case GF_GRAD: {
3459             rrd_value_t diffval = im->maxval - im->minval;
3460             rrd_value_t maxlimit = im->maxval + 9 * diffval;
3461             rrd_value_t minlimit = im->minval - 9 * diffval;        
3462             for (ii = 0; ii < im->xsize; ii++) {
3463                 /* fix data points at oo and -oo */
3464                 if (isinf(im->gdes[i].p_data[ii])) {
3465                     if (im->gdes[i].p_data[ii] > 0) {
3466                         im->gdes[i].p_data[ii] = im->maxval;
3467                     } else {
3468                         im->gdes[i].p_data[ii] = im->minval;
3469                     }
3470                 }
3471                 /* some versions of cairo go unstable when trying
3472                    to draw way out of the canvas ... lets not even try */
3473                if (im->gdes[i].p_data[ii] > maxlimit) {
3474                    im->gdes[i].p_data[ii] = maxlimit;
3475                }
3476                if (im->gdes[i].p_data[ii] < minlimit) {
3477                    im->gdes[i].p_data[ii] = minlimit;
3478                }
3479             }           /* for */
3480
3481             /* *******************************************************
3482                a           ___. (a,t)
3483                |   |    ___
3484                ____|   |   |   |
3485                |       |___|
3486                -------|--t-1--t--------------------------------
3487
3488                if we know the value at time t was a then
3489                we draw a square from t-1 to t with the value a.
3490
3491                ********************************************************* */
3492             if (im->gdes[i].col.alpha != 0.0) {
3493                 /* GF_LINE and friend */
3494                 if (im->gdes[i].gf == GF_LINE) {
3495                     double    last_y = 0.0;
3496                     int       draw_on = 0;
3497
3498                     cairo_save(im->cr);
3499                     cairo_new_path(im->cr);
3500                     cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3501                     if (im->gdes[i].dash) {
3502                         cairo_set_dash(im->cr,
3503                                        im->gdes[i].p_dashes,
3504                                        im->gdes[i].ndash, im->gdes[i].offset);
3505                     }
3506
3507                     for (ii = 1; ii < im->xsize; ii++) {
3508                         if (isnan(im->gdes[i].p_data[ii])
3509                             || (im->slopemode == 1
3510                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3511                             draw_on = 0;
3512                             continue;
3513                         }
3514                         if (draw_on == 0) {
3515                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3516                             if (im->slopemode == 0) {
3517                                 double    x = ii - 1 + im->xorigin;
3518                                 double    y = last_y;
3519
3520                                 gfx_line_fit(im, &x, &y);
3521                                 cairo_move_to(im->cr, x, y);
3522                                 x = ii + im->xorigin;
3523                                 y = last_y;
3524                                 gfx_line_fit(im, &x, &y);
3525                                 cairo_line_to(im->cr, x, y);
3526                             } else {
3527                                 double    x = ii - 1 + im->xorigin;
3528                                 double    y =
3529                                     ytr(im, im->gdes[i].p_data[ii - 1]);
3530                                 gfx_line_fit(im, &x, &y);
3531                                 cairo_move_to(im->cr, x, y);
3532                                 x = ii + im->xorigin;
3533                                 y = last_y;
3534                                 gfx_line_fit(im, &x, &y);
3535                                 cairo_line_to(im->cr, x, y);
3536                             }
3537                             draw_on = 1;
3538                         } else {
3539                             double    x1 = ii + im->xorigin;
3540                             double    y1 = ytr(im, im->gdes[i].p_data[ii]);
3541
3542                             if (im->slopemode == 0
3543                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3544                                 double    x = ii - 1 + im->xorigin;
3545                                 double    y = y1;
3546
3547                                 gfx_line_fit(im, &x, &y);
3548                                 cairo_line_to(im->cr, x, y);
3549                             };
3550                             last_y = y1;
3551                             gfx_line_fit(im, &x1, &y1);
3552                             cairo_line_to(im->cr, x1, y1);
3553                         };
3554                     }
3555                     cairo_set_source_rgba(im->cr,
3556                                           im->gdes[i].
3557                                           col.red,
3558                                           im->gdes[i].
3559                                           col.green,
3560                                           im->gdes[i].
3561                                           col.blue, im->gdes[i].col.alpha);
3562                     cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3563                     cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3564                     cairo_stroke(im->cr);
3565                     cairo_restore(im->cr);
3566                 } else {
3567                                         double lastx=0;
3568                                         double lasty=0;
3569                     int       idxI = -1;
3570                     double   *foreY =
3571                         (double *) malloc(sizeof(double) * im->xsize * 2);
3572                     double   *foreX =
3573                         (double *) malloc(sizeof(double) * im->xsize * 2);
3574                     double   *backY =
3575                         (double *) malloc(sizeof(double) * im->xsize * 2);
3576                     double   *backX =
3577                         (double *) malloc(sizeof(double) * im->xsize * 2);
3578                     int       drawem = 0;
3579
3580                     for (ii = 0; ii <= im->xsize; ii++) {
3581                         double    ybase, ytop;
3582
3583                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3584                             int       cntI = 1;
3585                             int       lastI = 0;
3586
3587                             while (cntI < idxI
3588                                    &&
3589                                    AlmostEqual2sComplement(foreY
3590                                                            [lastI],
3591                                                            foreY[cntI], 4)
3592                                    &&
3593                                    AlmostEqual2sComplement(foreY
3594                                                            [lastI],
3595                                                            foreY
3596                                                            [cntI + 1], 4)) {
3597                                 cntI++;
3598                             }
3599                                                         if (im->gdes[i].gf != GF_GRAD) {
3600                                 gfx_new_area(im,
3601                                              backX[0], backY[0],
3602                                              foreX[0], foreY[0],
3603                                              foreX[cntI],
3604                                              foreY[cntI], im->gdes[i].col);
3605                                                         } else {
3606                                                                 lastx = foreX[cntI];
3607                                                                 lasty = foreY[cntI];
3608                                                         }
3609                                                         while (cntI < idxI) {
3610                                 lastI = cntI;
3611                                 cntI++;
3612                                 while (cntI < idxI
3613                                        &&
3614                                        AlmostEqual2sComplement(foreY
3615                                                                [lastI],
3616                                                                foreY[cntI], 4)
3617                                        &&
3618                                        AlmostEqual2sComplement(foreY
3619                                                                [lastI],
3620                                                                foreY
3621                                                                [cntI
3622                                                                 + 1], 4)) {
3623                                     cntI++;
3624                                 }
3625                                                                 if (im->gdes[i].gf != GF_GRAD) {
3626                                         gfx_add_point(im, foreX[cntI], foreY[cntI]);
3627                                                                 } else {
3628                                                                         gfx_add_rect_fadey(im, 
3629                                                                                 lastx, foreY[0],
3630                                                                                 foreX[cntI], foreY[cntI], lasty, 
3631                                                                                 im->gdes[i].col,
3632                                                                                 im->gdes[i].col2,
3633                                                                                 im->gdes[i].gradheight
3634                                                                                 );
3635                                                                         lastx = foreX[cntI];
3636                                                                         lasty = foreY[cntI];
3637                                                                 }
3638                             }
3639                                                         if (im->gdes[i].gf != GF_GRAD) {
3640                                 gfx_add_point(im, backX[idxI], backY[idxI]);
3641                                                         } else {
3642                                                                 gfx_add_rect_fadey(im,
3643                                                                         lastx, foreY[0],
3644                                                                         backX[idxI], backY[idxI], lasty,
3645                                                                         im->gdes[i].col,
3646                                                                         im->gdes[i].col2,
3647                                                                         im->gdes[i].gradheight);
3648                                                                 lastx = backX[idxI];
3649                                                                 lasty = backY[idxI];
3650                                                         }
3651                             while (idxI > 1) {
3652                                 lastI = idxI;
3653                                 idxI--;
3654                                 while (idxI > 1
3655                                        &&
3656                                        AlmostEqual2sComplement(backY
3657                                                                [lastI],
3658                                                                backY[idxI], 4)
3659                                        &&
3660                                        AlmostEqual2sComplement(backY
3661                                                                [lastI],
3662                                                                backY
3663                                                                [idxI
3664                                                                 - 1], 4)) {
3665                                     idxI--;
3666                                 }
3667                                                                 if (im->gdes[i].gf != GF_GRAD) {
3668                                         gfx_add_point(im, backX[idxI], backY[idxI]);
3669                                                                 } else {
3670                                                                         gfx_add_rect_fadey(im,
3671                                                                                 lastx, foreY[0],
3672                                                                                 backX[idxI], backY[idxI], lasty,
3673                                                                                 im->gdes[i].col,
3674                                                                                 im->gdes[i].col2,
3675                                                                                 im->gdes[i].gradheight);
3676                                                                         lastx = backX[idxI];
3677                                                                         lasty = backY[idxI];
3678                                                                 }
3679                             }
3680                             idxI = -1;
3681                             drawem = 0;
3682                                                         if (im->gdes[i].gf != GF_GRAD) 
3683                                     gfx_close_path(im);
3684                         }
3685                         if (drawem != 0) {
3686                             drawem = 0;
3687                             idxI = -1;
3688                         }
3689                         if (ii == im->xsize)
3690                             break;
3691                         if (im->slopemode == 0 && ii == 0) {
3692                             continue;
3693                         }
3694                         if (isnan(im->gdes[i].p_data[ii])) {
3695                             drawem = 1;
3696                             continue;
3697                         }
3698                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3699                         if (lastgdes && im->gdes[i].stack) {
3700                             ybase = ytr(im, lastgdes->p_data[ii]);
3701                         } else {
3702                             ybase = ytr(im, areazero);
3703                         }
3704                         if (ybase == ytop) {
3705                             drawem = 1;
3706                             continue;
3707                         }
3708
3709                         if (ybase > ytop) {
3710                             double    extra = ytop;
3711
3712                             ytop = ybase;
3713                             ybase = extra;
3714                         }
3715                         if (im->slopemode == 0) {
3716                             backY[++idxI] = ybase - 0.2;
3717                             backX[idxI] = ii + im->xorigin - 1;
3718                             foreY[idxI] = ytop + 0.2;
3719                             foreX[idxI] = ii + im->xorigin - 1;
3720                         }
3721                         backY[++idxI] = ybase - 0.2;
3722                         backX[idxI] = ii + im->xorigin;
3723                         foreY[idxI] = ytop + 0.2;
3724                         foreX[idxI] = ii + im->xorigin;
3725                     }
3726                     /* close up any remaining area */
3727                     free(foreY);
3728                     free(foreX);
3729                     free(backY);
3730                     free(backX);
3731                 }       /* else GF_LINE */
3732             }
3733             /* if color != 0x0 */
3734             /* make sure we do not run into trouble when stacking on NaN */
3735             for (ii = 0; ii < im->xsize; ii++) {
3736                 if (isnan(im->gdes[i].p_data[ii])) {
3737                     if (lastgdes && (im->gdes[i].stack)) {
3738                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3739                     } else {
3740                         im->gdes[i].p_data[ii] = areazero;
3741                     }
3742                 }
3743             }
3744             lastgdes = &(im->gdes[i]);
3745             break;
3746         } /* GF_AREA, GF_LINE, GF_GRAD */
3747         case GF_STACK:
3748             rrd_set_error
3749                 ("STACK should already be turned into LINE or AREA here");
3750             return -1;
3751             break;
3752         }               /* switch */
3753     }
3754     cairo_reset_clip(im->cr);
3755
3756     /* grid_paint also does the text */
3757     if (!(im->extra_flags & ONLY_GRAPH))
3758         grid_paint(im);
3759     if (!(im->extra_flags & ONLY_GRAPH))
3760         axis_paint(im);
3761     /* the RULES are the last thing to paint ... */
3762     for (i = 0; i < im->gdes_c; i++) {
3763
3764         switch (im->gdes[i].gf) {
3765         case GF_HRULE:
3766             if (im->gdes[i].yrule >= im->minval
3767                 && im->gdes[i].yrule <= im->maxval) {
3768                 cairo_save(im->cr);
3769                 if (im->gdes[i].dash) {
3770                     cairo_set_dash(im->cr,
3771                                    im->gdes[i].p_dashes,
3772                                    im->gdes[i].ndash, im->gdes[i].offset);
3773                 }
3774                 gfx_line(im, im->xorigin,
3775                          ytr(im, im->gdes[i].yrule),
3776                          im->xorigin + im->xsize,
3777                          ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3778                 cairo_stroke(im->cr);
3779                 cairo_restore(im->cr);
3780             }
3781             break;
3782         case GF_VRULE:
3783             if (im->gdes[i].xrule >= im->start
3784                 && im->gdes[i].xrule <= im->end) {
3785                 cairo_save(im->cr);
3786                 if (im->gdes[i].dash) {
3787                     cairo_set_dash(im->cr,
3788                                    im->gdes[i].p_dashes,
3789                                    im->gdes[i].ndash, im->gdes[i].offset);
3790                 }
3791                 gfx_line(im,
3792                          xtr(im, im->gdes[i].xrule),
3793                          im->yorigin, xtr(im,
3794                                           im->
3795                                           gdes[i].
3796                                           xrule),
3797                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3798                 cairo_stroke(im->cr);
3799                 cairo_restore(im->cr);
3800             }
3801             break;
3802         default:
3803             break;
3804         }
3805     }
3806     /* close the graph via cairo*/
3807     return graph_cairo_finish(im);
3808 }
3809
3810 int graph_cairo_setup (image_desc_t *im)
3811 {
3812     /* the actual graph is created by going through the individual
3813        graph elements and then drawing them */
3814     cairo_surface_destroy(im->surface);
3815     switch (im->imgformat) {
3816     case IF_PNG:
3817         im->surface =
3818             cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3819                                        im->ximg * im->zoom,
3820                                        im->yimg * im->zoom);
3821         break;
3822     case IF_PDF:
3823         im->gridfit = 0;
3824         im->surface = strlen(im->graphfile)
3825             ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3826                                        im->yimg * im->zoom)
3827             : cairo_pdf_surface_create_for_stream
3828             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3829         break;
3830     case IF_EPS:
3831         im->gridfit = 0;
3832         im->surface = strlen(im->graphfile)
3833             ?
3834             cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3835                                     im->yimg * im->zoom)
3836             : cairo_ps_surface_create_for_stream
3837             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3838         break;
3839     case IF_SVG:
3840         im->gridfit = 0;
3841         im->surface = strlen(im->graphfile)
3842             ?
3843             cairo_svg_surface_create(im->
3844                                      graphfile,
3845                                      im->ximg * im->zoom, im->yimg * im->zoom)
3846             : cairo_svg_surface_create_for_stream
3847             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3848         cairo_svg_surface_restrict_to_version
3849             (im->surface, CAIRO_SVG_VERSION_1_1);
3850         break;
3851     case IF_XML:
3852     case IF_XMLENUM:
3853     case IF_CSV:
3854     case IF_TSV:
3855     case IF_SSV:
3856     case IF_JSON:
3857     case IF_JSONTIME:
3858         break;
3859     };
3860     cairo_destroy(im->cr);
3861     im->cr = cairo_create(im->surface);
3862     cairo_set_antialias(im->cr, im->graph_antialias);
3863     cairo_scale(im->cr, im->zoom, im->zoom);
3864 //    pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3865     gfx_new_area(im, 0, 0, 0, im->yimg,
3866                  im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3867     gfx_add_point(im, im->ximg, 0);
3868     gfx_close_path(im);
3869     gfx_new_area(im, im->xorigin,
3870                  im->yorigin,
3871                  im->xorigin +
3872                  im->xsize, im->yorigin,
3873                  im->xorigin +
3874                  im->xsize,
3875                  im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3876     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3877     gfx_close_path(im);
3878     cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3879                     im->xsize, im->ysize + 2.0);
3880     cairo_clip(im->cr);
3881     return 0;
3882 }
3883
3884 int graph_cairo_finish (image_desc_t *im)
3885 {
3886
3887     switch (im->imgformat) {
3888     case IF_PNG:
3889     {
3890         cairo_status_t status;
3891
3892         status = strlen(im->graphfile) ?
3893             cairo_surface_write_to_png(im->surface, im->graphfile)
3894             : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3895                                                 im);
3896
3897         if (status != CAIRO_STATUS_SUCCESS) {
3898             rrd_set_error("Could not save png to '%s'", im->graphfile);
3899             return 1;
3900         }
3901         break;
3902     }
3903     case IF_XML:
3904     case IF_XMLENUM:
3905     case IF_CSV:
3906     case IF_TSV:
3907     case IF_SSV:
3908     case IF_JSON:
3909     case IF_JSONTIME:
3910       break;
3911     default:
3912         if (strlen(im->graphfile)) {
3913             cairo_show_page(im->cr);
3914         } else {
3915             cairo_surface_finish(im->surface);
3916         }
3917         break;
3918     }
3919
3920     return 0;
3921 }
3922
3923 int graph_paint_xy(
3924                       image_desc_t *im, int lazy)
3925 {
3926   rrd_set_error("XY diagramm not implemented");  
3927   return -1;
3928 }
3929
3930 /*****************************************************
3931  * graph stuff
3932  *****************************************************/
3933
3934 int gdes_alloc(
3935     image_desc_t *im)
3936 {
3937
3938     im->gdes_c++;
3939     if ((im->gdes = (graph_desc_t *)
3940          rrd_realloc(im->gdes, (im->gdes_c)
3941                      * sizeof(graph_desc_t))) == NULL) {
3942         rrd_set_error("realloc graph_descs");
3943         return -1;
3944     }
3945
3946
3947     im->gdes[im->gdes_c - 1].step = im->step;
3948     im->gdes[im->gdes_c - 1].step_orig = im->step;
3949     im->gdes[im->gdes_c - 1].stack = 0;
3950     im->gdes[im->gdes_c - 1].linewidth = 0;
3951     im->gdes[im->gdes_c - 1].debug = 0;
3952     im->gdes[im->gdes_c - 1].start = im->start;
3953     im->gdes[im->gdes_c - 1].start_orig = im->start;
3954     im->gdes[im->gdes_c - 1].end = im->end;
3955     im->gdes[im->gdes_c - 1].end_orig = im->end;
3956     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3957     im->gdes[im->gdes_c - 1].data = NULL;
3958     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3959     im->gdes[im->gdes_c - 1].data_first = 0;
3960     im->gdes[im->gdes_c - 1].p_data = NULL;
3961     im->gdes[im->gdes_c - 1].rpnp = NULL;
3962     im->gdes[im->gdes_c - 1].p_dashes = NULL;
3963     im->gdes[im->gdes_c - 1].shift = 0.0;
3964     im->gdes[im->gdes_c - 1].dash = 0;
3965     im->gdes[im->gdes_c - 1].ndash = 0;
3966     im->gdes[im->gdes_c - 1].offset = 0;
3967     im->gdes[im->gdes_c - 1].col.red = 0.0;
3968     im->gdes[im->gdes_c - 1].col.green = 0.0;
3969     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3970     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3971     im->gdes[im->gdes_c - 1].col2.red = 0.0;
3972     im->gdes[im->gdes_c - 1].col2.green = 0.0;
3973     im->gdes[im->gdes_c - 1].col2.blue = 0.0;
3974     im->gdes[im->gdes_c - 1].col2.alpha = 0.0;
3975     im->gdes[im->gdes_c - 1].gradheight = 50.0;
3976     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3977     im->gdes[im->gdes_c - 1].format[0] = '\0';
3978     im->gdes[im->gdes_c - 1].strftm = 0;
3979     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3980     im->gdes[im->gdes_c - 1].ds = -1;
3981     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3982     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3983     im->gdes[im->gdes_c - 1].yrule = DNAN;
3984     im->gdes[im->gdes_c - 1].xrule = 0;
3985     im->gdes[im->gdes_c - 1].daemon[0] = 0;
3986     return 0;
3987 }
3988
3989 /* copies input untill the first unescaped colon is found
3990    or until input ends. backslashes have to be escaped as well */
3991 int scan_for_col(
3992     const char *const input,
3993     int len,
3994     char *const output)
3995 {
3996     int       inp, outp = 0;
3997
3998     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3999         if (input[inp] == '\\'
4000             && input[inp + 1] != '\0'
4001             && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
4002             output[outp++] = input[++inp];
4003         } else {
4004             output[outp++] = input[inp];
4005         }
4006     }
4007     output[outp] = '\0';
4008     return inp;
4009 }
4010
4011 /* Now just a wrapper around rrd_graph_v */
4012 int rrd_graph(
4013     int argc,
4014     char **argv,
4015     char ***prdata,
4016     int *xsize,
4017     int *ysize,
4018     FILE * stream,
4019     double *ymin,
4020     double *ymax)
4021 {
4022     int       prlines = 0;
4023     rrd_info_t *grinfo = NULL;
4024     rrd_info_t *walker;
4025
4026     grinfo = rrd_graph_v(argc, argv);
4027     if (grinfo == NULL)
4028         return -1;
4029     walker = grinfo;
4030     (*prdata) = NULL;
4031     while (walker) {
4032         if (strcmp(walker->key, "image_info") == 0) {
4033             prlines++;
4034             if (((*prdata) =
4035                  (char**)rrd_realloc((*prdata),
4036                              (prlines + 1) * sizeof(char *))) == NULL) {
4037                 rrd_set_error("realloc prdata");
4038                 return 0;
4039             }
4040             /* imginfo goes to position 0 in the prdata array */
4041             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
4042                                              + 2) * sizeof(char));
4043             strcpy((*prdata)[prlines - 1], walker->value.u_str);
4044             (*prdata)[prlines] = NULL;
4045         }
4046         /* skip anything else */
4047         walker = walker->next;
4048     }
4049     walker = grinfo;
4050     *xsize = 0;
4051     *ysize = 0;
4052     *ymin = 0;
4053     *ymax = 0;
4054     while (walker) {
4055         if (strcmp(walker->key, "image_width") == 0) {
4056             *xsize = walker->value.u_cnt;
4057         } else if (strcmp(walker->key, "image_height") == 0) {
4058             *ysize = walker->value.u_cnt;
4059         } else if (strcmp(walker->key, "value_min") == 0) {
4060             *ymin = walker->value.u_val;
4061         } else if (strcmp(walker->key, "value_max") == 0) {
4062             *ymax = walker->value.u_val;
4063         } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
4064             prlines++;
4065             if (((*prdata) =
4066                  (char**)rrd_realloc((*prdata),
4067                              (prlines + 1) * sizeof(char *))) == NULL) {
4068                 rrd_set_error("realloc prdata");
4069                 return 0;
4070             }
4071             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
4072                                              + 2) * sizeof(char));
4073             (*prdata)[prlines] = NULL;
4074             strcpy((*prdata)[prlines - 1], walker->value.u_str);
4075         } else if (strcmp(walker->key, "image") == 0) {
4076             if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
4077                    (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
4078                 rrd_set_error("writing image");
4079                 return 0;
4080             }
4081         }
4082         /* skip anything else */
4083         walker = walker->next;
4084     }
4085     rrd_info_free(grinfo);
4086     return 0;
4087 }
4088
4089
4090 /* Some surgery done on this function, it became ridiculously big.
4091 ** Things moved:
4092 ** - initializing     now in rrd_graph_init()
4093 ** - options parsing  now in rrd_graph_options()
4094 ** - script parsing   now in rrd_graph_script()
4095 */
4096
4097 rrd_info_t *rrd_graph_v(
4098     int argc,
4099     char **argv)
4100 {
4101     image_desc_t im;
4102     rrd_info_t *grinfo;
4103     char *old_locale;
4104     rrd_graph_init(&im);
4105     /* a dummy surface so that we can measure text sizes for placements */
4106     old_locale = setlocale(LC_NUMERIC, NULL);
4107     setlocale(LC_NUMERIC, "C");
4108     rrd_graph_options(argc, argv, &im);
4109     if (rrd_test_error()) {
4110         setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4111         rrd_info_free(im.grinfo);
4112         im_free(&im);
4113         return NULL;
4114     }
4115
4116     if (optind >= argc) {
4117         setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4118         rrd_info_free(im.grinfo);
4119         im_free(&im);
4120         rrd_set_error("missing filename");
4121         return NULL;
4122     }
4123
4124     if (strlen(argv[optind]) >= MAXPATH) {
4125         setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4126         rrd_set_error("filename (including path) too long");
4127         rrd_info_free(im.grinfo);
4128         im_free(&im);
4129         return NULL;
4130     }
4131
4132     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
4133     im.graphfile[MAXPATH - 1] = '\0';
4134
4135     if (strcmp(im.graphfile, "-") == 0) {
4136         im.graphfile[0] = '\0';
4137     }
4138
4139     rrd_graph_script(argc, argv, &im, 1);
4140     setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
4141
4142     if (rrd_test_error()) {
4143         rrd_info_free(im.grinfo);
4144         im_free(&im);
4145         return NULL;
4146     }
4147     
4148     /* Everything is now read and the actual work can start */
4149     if (graph_paint(&im) == -1) {
4150       rrd_info_free(im.grinfo);
4151       im_free(&im);
4152       return NULL;
4153     }
4154
4155     /* The image is generated and needs to be output.
4156      ** Also, if needed, print a line with information about the image.
4157      */
4158
4159     if (im.imginfo) {
4160         rrd_infoval_t info;
4161         char     *path;
4162         char     *filename;
4163
4164         path = strdup(im.graphfile);
4165         filename = basename(path);
4166         info.u_str =
4167             sprintf_alloc(im.imginfo,
4168                           filename,
4169                           (long) (im.zoom *
4170                                   im.ximg), (long) (im.zoom * im.yimg));
4171         grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
4172         free(info.u_str);
4173         free(path);
4174     }
4175     if (im.rendered_image) {
4176         rrd_infoval_t img;
4177
4178         img.u_blo.size = im.rendered_image_size;
4179         img.u_blo.ptr = im.rendered_image;
4180         grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
4181     }
4182     grinfo = im.grinfo;
4183     im_free(&im);
4184     return grinfo;
4185 }
4186
4187 static void
4188 rrd_set_font_desc (
4189     image_desc_t *im,int prop,char *font, double size ){
4190     if (font){
4191         strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4192         im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4193         /* if we already got one, drop it first */
4194         pango_font_description_free(im->text_prop[prop].font_desc);
4195         im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4196     };
4197     if (size > 0){
4198         im->text_prop[prop].size = size;
4199     };
4200     if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4201         pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4202     };
4203 }
4204
4205 void rrd_graph_init(
4206     image_desc_t
4207     *im)
4208 {
4209     unsigned int i;
4210     char     *deffont = getenv("RRD_DEFAULT_FONT");
4211     static PangoFontMap *fontmap = NULL;
4212     PangoContext *context;
4213
4214     /* zero the whole structure first */
4215     memset(im,0,sizeof(image_desc_t));
4216
4217 #ifdef HAVE_TZSET
4218     tzset();
4219 #endif
4220     im->graph_type = GTYPE_TIME;
4221     im->base = 1000;
4222     im->daemon_addr = NULL;
4223     im->draw_x_grid = 1;
4224     im->draw_y_grid = 1;
4225     im->draw_3d_border = 2;
4226     im->dynamic_labels = 0;
4227     im->extra_flags = 0;
4228     im->font_options = cairo_font_options_create();
4229     im->forceleftspace = 0;
4230     im->gdes_c = 0;
4231     im->gdes = NULL;
4232     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4233     im->grid_dash_off = 1;
4234     im->grid_dash_on = 1;
4235     im->gridfit = 1;
4236     im->grinfo = (rrd_info_t *) NULL;
4237     im->grinfo_current = (rrd_info_t *) NULL;
4238     im->imgformat = IF_PNG;
4239     im->imginfo = NULL;
4240     im->lazy = 0;
4241     im->legenddirection = TOP_DOWN;
4242     im->legendheight = 0;
4243     im->legendposition = SOUTH;
4244     im->legendwidth = 0;
4245     im->logarithmic = 0;
4246     im->maxval = DNAN;
4247     im->minval = 0;
4248     im->minval = DNAN;
4249     im->magfact = 1;
4250     im->prt_c = 0;
4251     im->rigid = 0;
4252     im->rendered_image_size = 0;
4253     im->rendered_image = NULL;
4254     im->slopemode = 0;
4255     im->step = 0;
4256     im->symbol = ' ';
4257     im->tabwidth = 40.0;
4258     im->title[0] = '\0';
4259     im->unitsexponent = 9999;
4260     im->unitslength = 6;
4261     im->viewfactor = 1.0;
4262     im->watermark[0] = '\0';
4263     im->with_markup = 0;
4264     im->ximg = 0;
4265     im->xlab_user.minsec = -1;
4266     im->xorigin = 0;
4267     im->xOriginLegend = 0;
4268     im->xOriginLegendY = 0;
4269     im->xOriginLegendY2 = 0;
4270     im->xOriginTitle = 0;
4271     im->xsize = 400;
4272     im->ygridstep = DNAN;
4273     im->yimg = 0;
4274     im->ylegend[0] = '\0';
4275     im->second_axis_scale = 0; /* 0 disables it */
4276     im->second_axis_shift = 0; /* no shift by default */
4277     im->second_axis_legend[0] = '\0';
4278     im->second_axis_format[0] = '\0';
4279     im->yorigin = 0;
4280     im->yOriginLegend = 0;
4281     im->yOriginLegendY = 0;
4282     im->yOriginLegendY2 = 0;
4283     im->yOriginTitle = 0;
4284     im->ysize = 100;
4285     im->zoom = 1;
4286
4287     im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4288     im->cr = cairo_create(im->surface);
4289
4290     for (i = 0; i < DIM(text_prop); i++) {
4291         im->text_prop[i].size = -1;
4292         im->text_prop[i].font_desc = NULL;
4293         rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4294     }
4295
4296     if (fontmap == NULL){
4297         fontmap = pango_cairo_font_map_get_default();
4298     }
4299
4300     context =  pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4301
4302     pango_cairo_context_set_resolution(context, 100);
4303
4304     pango_cairo_update_context(im->cr,context);
4305
4306     im->layout = pango_layout_new(context);
4307     g_object_unref (context);
4308
4309 //  im->layout = pango_cairo_create_layout(im->cr);
4310
4311
4312     cairo_font_options_set_hint_style
4313         (im->font_options, CAIRO_HINT_STYLE_FULL);
4314     cairo_font_options_set_hint_metrics
4315         (im->font_options, CAIRO_HINT_METRICS_ON);
4316     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4317
4318
4319
4320     for (i = 0; i < DIM(graph_col); i++)
4321         im->graph_col[i] = graph_col[i];
4322
4323
4324 }
4325
4326
4327 void rrd_graph_options(
4328     int argc,
4329     char *argv[],
4330     image_desc_t
4331     *im)
4332 {
4333     int       stroff;
4334     char     *parsetime_error = NULL;
4335     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4336     time_t    start_tmp = 0, end_tmp = 0;
4337     long      long_tmp;
4338     rrd_time_value_t start_tv, end_tv;
4339     long unsigned int color;
4340
4341     /* defines for long options without a short equivalent. should be bytes,
4342        and may not collide with (the ASCII value of) short options */
4343 #define LONGOPT_UNITS_SI 255
4344
4345 /* *INDENT-OFF* */
4346     struct option long_options[] = {
4347         { "alt-autoscale",      no_argument,       0, 'A'},
4348         { "imgformat",          required_argument, 0, 'a'},
4349         { "font-smoothing-threshold", required_argument, 0, 'B'},
4350         { "base",               required_argument, 0, 'b'},
4351         { "color",              required_argument, 0, 'c'},
4352         { "full-size-mode",     no_argument,       0, 'D'},
4353         { "daemon",             required_argument, 0, 'd'},
4354         { "slope-mode",         no_argument,       0, 'E'},
4355         { "end",                required_argument, 0, 'e'},
4356         { "force-rules-legend", no_argument,       0, 'F'},
4357         { "imginfo",            required_argument, 0, 'f'},
4358         { "graph-render-mode",  required_argument, 0, 'G'},
4359         { "no-legend",          no_argument,       0, 'g'},
4360         { "height",             required_argument, 0, 'h'},
4361         { "no-minor",           no_argument,       0, 'I'},
4362         { "interlaced",         no_argument,       0, 'i'},
4363         { "alt-autoscale-min",  no_argument,       0, 'J'},
4364         { "only-graph",         no_argument,       0, 'j'},
4365         { "units-length",       required_argument, 0, 'L'},
4366         { "lower-limit",        required_argument, 0, 'l'},
4367         { "alt-autoscale-max",  no_argument,       0, 'M'},
4368         { "zoom",               required_argument, 0, 'm'},
4369         { "no-gridfit",         no_argument,       0, 'N'},
4370         { "font",               required_argument, 0, 'n'},
4371         { "logarithmic",        no_argument,       0, 'o'},
4372         { "pango-markup",       no_argument,       0, 'P'},
4373         { "font-render-mode",   required_argument, 0, 'R'},
4374         { "rigid",              no_argument,       0, 'r'},
4375         { "step",               required_argument, 0, 'S'},
4376         { "start",              required_argument, 0, 's'},
4377         { "tabwidth",           required_argument, 0, 'T'},
4378         { "title",              required_argument, 0, 't'},
4379         { "upper-limit",        required_argument, 0, 'u'},
4380         { "vertical-label",     required_argument, 0, 'v'},
4381         { "watermark",          required_argument, 0, 'W'},
4382         { "width",              required_argument, 0, 'w'},
4383         { "units-exponent",     required_argument, 0, 'X'},
4384         { "x-grid",             required_argument, 0, 'x'},
4385         { "alt-y-grid",         no_argument,       0, 'Y'},
4386         { "y-grid",             required_argument, 0, 'y'},
4387         { "lazy",               no_argument,       0, 'z'},
4388         { "units",              required_argument, 0, LONGOPT_UNITS_SI},
4389         { "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 */
4390         { "disable-rrdtool-tag",no_argument,       0, 1001},
4391         { "right-axis",         required_argument, 0, 1002},
4392         { "right-axis-label",   required_argument, 0, 1003},
4393         { "right-axis-format",  required_argument, 0, 1004},
4394         { "legend-position",    required_argument, 0, 1005},
4395         { "legend-direction",   required_argument, 0, 1006},
4396         { "border",             required_argument, 0, 1007},
4397         { "grid-dash",          required_argument, 0, 1008},
4398         { "dynamic-labels",     no_argument,       0, 1009},
4399         { "week-fmt",           required_argument, 0, 1010},
4400         { "graph-type",         required_argument, 0, 1011},
4401         {  0, 0, 0, 0}
4402 };
4403 /* *INDENT-ON* */
4404
4405     optind = 0;
4406     opterr = 0;         /* initialize getopt */
4407     rrd_parsetime("end-24h", &start_tv);
4408     rrd_parsetime("now", &end_tv);
4409     while (1) {
4410         int       option_index = 0;
4411         int       opt;
4412         int       col_start, col_end;
4413
4414         opt = getopt_long(argc, argv,
4415                           "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",
4416                           long_options, &option_index);
4417         if (opt == EOF)
4418             break;
4419         switch (opt) {
4420         case 'I':
4421             im->extra_flags |= NOMINOR;
4422             break;
4423         case 'Y':
4424             im->extra_flags |= ALTYGRID;
4425             break;
4426         case 'A':
4427             im->extra_flags |= ALTAUTOSCALE;
4428             break;
4429         case 'J':
4430             im->extra_flags |= ALTAUTOSCALE_MIN;
4431             break;
4432         case 'M':
4433             im->extra_flags |= ALTAUTOSCALE_MAX;
4434             break;
4435         case 'j':
4436             im->extra_flags |= ONLY_GRAPH;
4437             break;
4438         case 'g':
4439             im->extra_flags |= NOLEGEND;
4440             break;
4441         case 1005:
4442             if (strcmp(optarg, "north") == 0) {
4443                 im->legendposition = NORTH;
4444             } else if (strcmp(optarg, "west") == 0) {
4445                 im->legendposition = WEST;
4446             } else if (strcmp(optarg, "south") == 0) {
4447                 im->legendposition = SOUTH;
4448             } else if (strcmp(optarg, "east") == 0) {
4449                 im->legendposition = EAST;
4450             } else {
4451                 rrd_set_error("unknown legend-position '%s'", optarg);
4452                 return;
4453             }
4454             break;
4455         case 1006:
4456             if (strcmp(optarg, "topdown") == 0) {
4457                 im->legenddirection = TOP_DOWN;
4458             } else if (strcmp(optarg, "bottomup") == 0) {
4459                 im->legenddirection = BOTTOM_UP;
4460             } else {
4461                 rrd_set_error("unknown legend-position '%s'", optarg);
4462                 return;
4463             }
4464             break;
4465         case 'F':
4466             im->extra_flags |= FORCE_RULES_LEGEND;
4467             break;
4468         case 1001:
4469             im->extra_flags |= NO_RRDTOOL_TAG;
4470             break;
4471         case LONGOPT_UNITS_SI:
4472             if (im->extra_flags & FORCE_UNITS) {
4473                 rrd_set_error("--units can only be used once!");
4474                 return;
4475             }
4476             if (strcmp(optarg, "si") == 0)
4477                 im->extra_flags |= FORCE_UNITS_SI;
4478             else {
4479                 rrd_set_error("invalid argument for --units: %s", optarg);
4480                 return;
4481             }
4482             break;
4483         case 'X':
4484             im->unitsexponent = atoi(optarg);
4485             break;
4486         case 'L':
4487             im->unitslength = atoi(optarg);
4488             im->forceleftspace = 1;
4489             break;
4490         case 'T':
4491             im->tabwidth = atof(optarg);
4492             break;
4493         case 'S':
4494             im->step = atoi(optarg);
4495             break;
4496         case 'N':
4497             im->gridfit = 0;
4498             break;
4499         case 'P':
4500             im->with_markup = 1;
4501             break;
4502         case 's':
4503             if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4504                 rrd_set_error("start time: %s", parsetime_error);
4505                 return;
4506             }
4507             break;
4508         case 'e':
4509             if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4510                 rrd_set_error("end time: %s", parsetime_error);
4511                 return;
4512             }
4513             break;
4514         case 'x':
4515             if (strcmp(optarg, "none") == 0) {
4516                 im->draw_x_grid = 0;
4517                 break;
4518             };
4519             if (sscanf(optarg,
4520                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4521                        scan_gtm,
4522                        &im->xlab_user.gridst,
4523                        scan_mtm,
4524                        &im->xlab_user.mgridst,
4525                        scan_ltm,
4526                        &im->xlab_user.labst,
4527                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4528                 strncpy(im->xlab_form, optarg + stroff,
4529                         sizeof(im->xlab_form) - 1);
4530                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4531                 if ((int)
4532                     (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4533                     rrd_set_error("unknown keyword %s", scan_gtm);
4534                     return;
4535                 } else if ((int)
4536                            (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4537                            == -1) {
4538                     rrd_set_error("unknown keyword %s", scan_mtm);
4539                     return;
4540                 } else if ((int)
4541                            (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4542                     rrd_set_error("unknown keyword %s", scan_ltm);
4543                     return;
4544                 }
4545                 im->xlab_user.minsec = 1;
4546                 im->xlab_user.stst = im->xlab_form;
4547             } else {
4548                 rrd_set_error("invalid x-grid format");
4549                 return;
4550             }
4551             break;
4552         case 'y':
4553
4554             if (strcmp(optarg, "none") == 0) {
4555                 im->draw_y_grid = 0;
4556                 break;
4557             };
4558             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4559                 if (im->ygridstep <= 0) {
4560                     rrd_set_error("grid step must be > 0");
4561                     return;
4562                 } else if (im->ylabfact < 1) {
4563                     rrd_set_error("label factor must be > 0");
4564                     return;
4565                 }
4566             } else {
4567                 rrd_set_error("invalid y-grid format");
4568                 return;
4569             }
4570             break;
4571         case 1007:
4572             im->draw_3d_border = atoi(optarg);
4573             break;
4574         case 1008: /* grid-dash */
4575             if(sscanf(optarg,
4576                       "%lf:%lf",
4577                       &im->grid_dash_on,
4578                       &im->grid_dash_off) != 2) {
4579                 rrd_set_error("expected grid-dash format float:float");
4580                 return;
4581             }
4582             break;   
4583         case 1009: /* enable dynamic labels */
4584             im->dynamic_labels = 1;
4585             break;         
4586         case 1010:
4587             strncpy(week_fmt,optarg,sizeof week_fmt);
4588             week_fmt[(sizeof week_fmt)-1]='\0';
4589             break;
4590         case 1002: /* right y axis */
4591
4592             if(sscanf(optarg,
4593                       "%lf:%lf",
4594                       &im->second_axis_scale,
4595                       &im->second_axis_shift) == 2) {
4596                 if(im->second_axis_scale==0){
4597                     rrd_set_error("the second_axis_scale  must not be 0");
4598                     return;
4599                 }
4600             } else {
4601                 rrd_set_error("invalid right-axis format expected scale:shift");
4602                 return;
4603             }
4604             break;
4605         case 1003:
4606             strncpy(im->second_axis_legend,optarg,150);
4607             im->second_axis_legend[150]='\0';
4608             break;
4609         case 1004:
4610             if (bad_format(optarg)){
4611                 rrd_set_error("use either %le or %lf formats");
4612                 return;
4613             }
4614             strncpy(im->second_axis_format,optarg,150);
4615             im->second_axis_format[150]='\0';
4616             break;
4617         case 'v':
4618             strncpy(im->ylegend, optarg, 150);
4619             im->ylegend[150] = '\0';
4620             break;
4621         case 'u':
4622             im->maxval = atof(optarg);
4623             break;
4624         case 'l':
4625             im->minval = atof(optarg);
4626             break;
4627         case 'b':
4628             im->base = atol(optarg);
4629             if (im->base != 1024 && im->base != 1000) {
4630                 rrd_set_error
4631                     ("the only sensible value for base apart from 1000 is 1024");
4632                 return;
4633             }
4634             break;
4635         case 'w':
4636             long_tmp = atol(optarg);
4637             if (long_tmp < 10) {
4638                 rrd_set_error("width below 10 pixels");
4639                 return;
4640             }
4641             im->xsize = long_tmp;
4642             break;
4643         case 'h':
4644             long_tmp = atol(optarg);
4645             if (long_tmp < 10) {
4646                 rrd_set_error("height below 10 pixels");
4647                 return;
4648             }
4649             im->ysize = long_tmp;
4650             break;
4651         case 'D':
4652             im->extra_flags |= FULL_SIZE_MODE;
4653             break;
4654         case 'i':
4655             /* interlaced png not supported at the moment */
4656             break;
4657         case 'r':
4658             im->rigid = 1;
4659             break;
4660         case 'f':
4661             im->imginfo = optarg;
4662             break;
4663         case 'a':
4664             if ((int)
4665                 (im->imgformat = if_conv(optarg)) == -1) {
4666                 rrd_set_error("unsupported graphics format '%s'", optarg);
4667                 return;
4668             }
4669             break;
4670         case 1011:
4671             if ((int)
4672                 (im->graph_type = type_conv(optarg)) == -1) {
4673                 rrd_set_error("unsupported graphics type '%s'", optarg);
4674                 return;
4675             }
4676             break;
4677         case 'z':
4678             im->lazy = 1;
4679             break;
4680         case 'E':
4681             im->slopemode = 1;
4682             break;
4683         case 'o':
4684             im->logarithmic = 1;
4685             break;
4686         case 'c':
4687             if (sscanf(optarg,
4688                        "%10[A-Z]#%n%8lx%n",
4689                        col_nam, &col_start, &color, &col_end) == 2) {
4690                 int       ci;
4691                 int       col_len = col_end - col_start;
4692
4693                 switch (col_len) {
4694                 case 3:
4695                     color =
4696                         (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4697                                                          0x011000) |
4698                          ((color & 0x00F)
4699                           * 0x001100)
4700                          | 0x000000FF);
4701                     break;
4702                 case 4:
4703                     color =
4704                         (((color & 0xF000) *
4705                           0x11000) | ((color & 0x0F00) *
4706                                       0x01100) | ((color &
4707                                                    0x00F0) *
4708                                                   0x00110) |
4709                          ((color & 0x000F) * 0x00011)
4710                         );
4711                     break;
4712                 case 6:
4713                     color = (color << 8) + 0xff /* shift left by 8 */ ;
4714                     break;
4715                 case 8:
4716                     break;
4717                 default:
4718                     rrd_set_error("the color format is #RRGGBB[AA]");
4719                     return;
4720                 }
4721                 if ((ci = grc_conv(col_nam)) != -1) {
4722                     im->graph_col[ci] = gfx_hex_to_col(color);
4723                 } else {
4724                     rrd_set_error("invalid color name '%s'", col_nam);
4725                     return;
4726                 }
4727             } else {
4728                 rrd_set_error("invalid color def format");
4729                 return;
4730             }
4731             break;
4732         case 'n':{
4733             char      prop[15];
4734             double    size = 1;
4735             int       end;
4736
4737             if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4738                 int       sindex, propidx;
4739
4740                 if ((sindex = text_prop_conv(prop)) != -1) {
4741                     for (propidx = sindex;
4742                          propidx < TEXT_PROP_LAST; propidx++) {
4743                         if (size > 0) {
4744                             rrd_set_font_desc(im,propidx,NULL,size);
4745                         }
4746                         if ((int) strlen(optarg) > end+2) {
4747                             if (optarg[end] == ':') {
4748                                 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4749                             } else {
4750                                 rrd_set_error
4751                                     ("expected : after font size in '%s'",
4752                                      optarg);
4753                                 return;
4754                             }
4755                         }
4756                         /* only run the for loop for DEFAULT (0) for
4757                            all others, we break here. woodo programming */
4758                         if (propidx == sindex && sindex != 0)
4759                             break;
4760                     }
4761                 } else {
4762                     rrd_set_error("invalid fonttag '%s'", prop);
4763                     return;
4764                 }
4765             } else {
4766                 rrd_set_error("invalid text property format");
4767                 return;
4768             }
4769             break;
4770         }
4771         case 'm':
4772             im->zoom = atof(optarg);
4773             if (im->zoom <= 0.0) {
4774                 rrd_set_error("zoom factor must be > 0");
4775                 return;
4776             }
4777             break;
4778         case 't':
4779             strncpy(im->title, optarg, 150);
4780             im->title[150] = '\0';
4781             break;
4782         case 'R':
4783             if (strcmp(optarg, "normal") == 0) {
4784                 cairo_font_options_set_antialias
4785                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4786                 cairo_font_options_set_hint_style
4787                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4788             } else if (strcmp(optarg, "light") == 0) {
4789                 cairo_font_options_set_antialias
4790                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4791                 cairo_font_options_set_hint_style
4792                     (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4793             } else if (strcmp(optarg, "mono") == 0) {
4794                 cairo_font_options_set_antialias
4795                     (im->font_options, CAIRO_ANTIALIAS_NONE);
4796                 cairo_font_options_set_hint_style
4797                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4798             } else {
4799                 rrd_set_error("unknown font-render-mode '%s'", optarg);
4800                 return;
4801             }
4802             break;
4803         case 'G':
4804             if (strcmp(optarg, "normal") == 0)
4805                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4806             else if (strcmp(optarg, "mono") == 0)
4807                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4808             else {
4809                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4810                 return;
4811             }
4812             break;
4813         case 'B':
4814             /* not supported curently */
4815             break;
4816         case 'W':
4817             strncpy(im->watermark, optarg, 100);
4818             im->watermark[99] = '\0';
4819             break;
4820         case 'd':
4821         {
4822             if (im->daemon_addr != NULL)
4823             {
4824                 rrd_set_error ("You cannot specify --daemon "
4825                         "more than once.");
4826                 return;
4827             }
4828
4829             im->daemon_addr = strdup(optarg);
4830             if (im->daemon_addr == NULL)
4831             {
4832               rrd_set_error("strdup failed");
4833               return;
4834             }
4835
4836             break;
4837         }
4838         case '?':
4839             if (optopt != 0)
4840                 rrd_set_error("unknown option '%c'", optopt);
4841             else
4842                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4843             return;
4844         }
4845     } /* while (1) */
4846
4847     pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4848     pango_layout_context_changed(im->layout);
4849
4850
4851
4852     if (im->logarithmic && im->minval <= 0) {
4853         rrd_set_error
4854             ("for a logarithmic yaxis you must specify a lower-limit > 0");
4855         return;
4856     }
4857
4858     if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4859         /* error string is set in rrd_parsetime.c */
4860         return;
4861     }
4862
4863     if (start_tmp < 3600 * 24 * 365 * 10) {
4864         rrd_set_error
4865             ("the first entry to fetch should be after 1980 (%ld)",
4866              start_tmp);
4867         return;
4868     }
4869
4870     if (end_tmp < start_tmp) {
4871         rrd_set_error
4872             ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4873         return;
4874     }
4875
4876     im->start = start_tmp;
4877     im->end = end_tmp;
4878     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4879 }
4880
4881 int rrd_graph_color(
4882     image_desc_t
4883     *im,
4884     char *var,
4885     char *err,
4886     int optional)
4887 {
4888     char     *color;
4889     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4890
4891     color = strstr(var, "#");
4892     if (color == NULL) {
4893         if (optional == 0) {
4894             rrd_set_error("Found no color in %s", err);
4895             return 0;
4896         }
4897         return 0;
4898     } else {
4899         int       n = 0;
4900         char     *rest;
4901         long unsigned int col;
4902
4903         rest = strstr(color, ":");
4904         if (rest != NULL)
4905             n = rest - color;
4906         else
4907             n = strlen(color);
4908         switch (n) {
4909         case 7:
4910             sscanf(color, "#%6lx%n", &col, &n);
4911             col = (col << 8) + 0xff /* shift left by 8 */ ;
4912             if (n != 7)
4913                 rrd_set_error("Color problem in %s", err);
4914             break;
4915         case 9:
4916             sscanf(color, "#%8lx%n", &col, &n);
4917             if (n == 9)
4918                 break;
4919         default:
4920             rrd_set_error("Color problem in %s", err);
4921         }
4922         if (rrd_test_error())
4923             return 0;
4924         gdp->col = gfx_hex_to_col(col);
4925         return n;
4926     }
4927 }
4928
4929
4930 int bad_format(
4931     char *fmt)
4932 {
4933     char     *ptr;
4934     int       n = 0;
4935
4936     ptr = fmt;
4937     while (*ptr != '\0')
4938         if (*ptr++ == '%') {
4939
4940             /* line cannot end with percent char */
4941             if (*ptr == '\0')
4942                 return 1;
4943             /* '%s', '%S' and '%%' are allowed */
4944             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4945                 ptr++;
4946             /* %c is allowed (but use only with vdef!) */
4947             else if (*ptr == 'c') {
4948                 ptr++;
4949                 n = 1;
4950             }
4951
4952             /* or else '% 6.2lf' and such are allowed */
4953             else {
4954                 /* optional padding character */
4955                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4956                     ptr++;
4957                 /* This should take care of 'm.n' with all three optional */
4958                 while (*ptr >= '0' && *ptr <= '9')
4959                     ptr++;
4960                 if (*ptr == '.')
4961                     ptr++;
4962                 while (*ptr >= '0' && *ptr <= '9')
4963                     ptr++;
4964                 /* Either 'le', 'lf' or 'lg' must follow here */
4965                 if (*ptr++ != 'l')
4966                     return 1;
4967                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4968                     ptr++;
4969                 else
4970                     return 1;
4971                 n++;
4972             }
4973         }
4974
4975     return (n != 1);
4976 }
4977
4978
4979 int vdef_parse(
4980     struct graph_desc_t
4981     *gdes,
4982     const char *const str)
4983 {
4984     /* A VDEF currently is either "func" or "param,func"
4985      * so the parsing is rather simple.  Change if needed.
4986      */
4987     double    param;
4988     char      func[30];
4989     int       n;
4990
4991     n = 0;
4992     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4993     if (n == (int) strlen(str)) {   /* matched */
4994         ;
4995     } else {
4996         n = 0;
4997         sscanf(str, "%29[A-Z]%n", func, &n);
4998         if (n == (int) strlen(str)) {   /* matched */
4999             param = DNAN;
5000         } else {
5001             rrd_set_error
5002                 ("Unknown function string '%s' in VDEF '%s'",
5003                  str, gdes->vname);
5004             return -1;
5005         }
5006     }
5007     if (!strcmp("PERCENT", func))
5008         gdes->vf.op = VDEF_PERCENT;
5009     else if (!strcmp("PERCENTNAN", func))
5010         gdes->vf.op = VDEF_PERCENTNAN;
5011     else if (!strcmp("MAXIMUM", func))
5012         gdes->vf.op = VDEF_MAXIMUM;
5013     else if (!strcmp("AVERAGE", func))
5014         gdes->vf.op = VDEF_AVERAGE;
5015     else if (!strcmp("STDEV", func))
5016         gdes->vf.op = VDEF_STDEV;
5017     else if (!strcmp("MINIMUM", func))
5018         gdes->vf.op = VDEF_MINIMUM;
5019     else if (!strcmp("TOTAL", func))
5020         gdes->vf.op = VDEF_TOTAL;
5021     else if (!strcmp("FIRST", func))
5022         gdes->vf.op = VDEF_FIRST;
5023     else if (!strcmp("LAST", func))
5024         gdes->vf.op = VDEF_LAST;
5025     else if (!strcmp("LSLSLOPE", func))
5026         gdes->vf.op = VDEF_LSLSLOPE;
5027     else if (!strcmp("LSLINT", func))
5028         gdes->vf.op = VDEF_LSLINT;
5029     else if (!strcmp("LSLCORREL", func))
5030         gdes->vf.op = VDEF_LSLCORREL;
5031     else {
5032         rrd_set_error
5033             ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
5034         return -1;
5035     };
5036     switch (gdes->vf.op) {
5037     case VDEF_PERCENT:
5038     case VDEF_PERCENTNAN:
5039         if (isnan(param)) { /* no parameter given */
5040             rrd_set_error
5041                 ("Function '%s' needs parameter in VDEF '%s'\n",
5042                  func, gdes->vname);
5043             return -1;
5044         };
5045         if (param >= 0.0 && param <= 100.0) {
5046             gdes->vf.param = param;
5047             gdes->vf.val = DNAN;    /* undefined */
5048             gdes->vf.when = 0;  /* undefined */
5049             gdes->vf.never = 1;
5050         } else {
5051             rrd_set_error
5052                 ("Parameter '%f' out of range in VDEF '%s'\n",
5053                  param, gdes->vname);
5054             return -1;
5055         };
5056         break;
5057     case VDEF_MAXIMUM:
5058     case VDEF_AVERAGE:
5059     case VDEF_STDEV:
5060     case VDEF_MINIMUM:
5061     case VDEF_TOTAL:
5062     case VDEF_FIRST:
5063     case VDEF_LAST:
5064     case VDEF_LSLSLOPE:
5065     case VDEF_LSLINT:
5066     case VDEF_LSLCORREL:
5067         if (isnan(param)) {
5068             gdes->vf.param = DNAN;
5069             gdes->vf.val = DNAN;
5070             gdes->vf.when = 0;
5071             gdes->vf.never = 1;
5072         } else {
5073             rrd_set_error
5074                 ("Function '%s' needs no parameter in VDEF '%s'\n",
5075                  func, gdes->vname);
5076             return -1;
5077         };
5078         break;
5079     };
5080     return 0;
5081 }
5082
5083
5084 int vdef_calc(
5085     image_desc_t *im,
5086     int gdi)
5087 {
5088     graph_desc_t *src, *dst;
5089     rrd_value_t *data;
5090     long      step, steps;
5091
5092     dst = &im->gdes[gdi];
5093     src = &im->gdes[dst->vidx];
5094     data = src->data + src->ds;
5095
5096     steps = (src->end - src->start) / src->step;
5097 #if 0
5098     printf
5099         ("DEBUG: start == %lu, end == %lu, %lu steps\n",
5100          src->start, src->end, steps);
5101 #endif
5102     switch (dst->vf.op) {
5103     case VDEF_PERCENT:{
5104         rrd_value_t *array;
5105         int       field;
5106         if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
5107             rrd_set_error("malloc VDEV_PERCENT");
5108             return -1;
5109         }
5110         for (step = 0; step < steps; step++) {
5111             array[step] = data[step * src->ds_cnt];
5112         }
5113         qsort(array, step, sizeof(double), vdef_percent_compar);
5114         field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
5115         dst->vf.val = array[field];
5116         dst->vf.when = 0;   /* no time component */
5117         dst->vf.never = 1;
5118         free(array);
5119 #if 0
5120         for (step = 0; step < steps; step++)
5121             printf("DEBUG: %3li:%10.2f %c\n",
5122                    step, array[step], step == field ? '*' : ' ');
5123 #endif
5124     }
5125         break;
5126     case VDEF_PERCENTNAN:{
5127         rrd_value_t *array;
5128         int       field;
5129        /* count number of "valid" values */
5130        int nancount=0;
5131        for (step = 0; step < steps; step++) {
5132          if (!isnan(data[step * src->ds_cnt])) { nancount++; }
5133        }
5134        /* and allocate it */
5135         if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
5136             rrd_set_error("malloc VDEV_PERCENT");
5137             return -1;
5138         }
5139        /* and fill it in */
5140        field=0;
5141         for (step = 0; step < steps; step++) {
5142            if (!isnan(data[step * src->ds_cnt])) {
5143                 array[field] = data[step * src->ds_cnt];
5144                field++;
5145             }
5146         }
5147         qsort(array, nancount, sizeof(double), vdef_percent_compar);
5148         field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
5149         dst->vf.val = array[field];
5150         dst->vf.when = 0;   /* no time component */
5151         dst->vf.never = 1;
5152         free(array);
5153     }
5154         break;
5155     case VDEF_MAXIMUM:
5156         step = 0;
5157         while (step != steps && isnan(data[step * src->ds_cnt]))
5158             step++;
5159         if (step == steps) {
5160             dst->vf.val = DNAN;
5161             dst->vf.when = 0;
5162             dst->vf.never = 1;
5163         } else {
5164             dst->vf.val = data[step * src->ds_cnt];
5165             dst->vf.when = src->start + (step + 1) * src->step;
5166             dst->vf.never = 0;
5167         }
5168         while (step != steps) {
5169             if (finite(data[step * src->ds_cnt])) {
5170                 if (data[step * src->ds_cnt] > dst->vf.val) {
5171                     dst->vf.val = data[step * src->ds_cnt];
5172                     dst->vf.when = src->start + (step + 1) * src->step;
5173                     dst->vf.never = 0;
5174                 }
5175             }
5176             step++;
5177         }
5178         break;
5179     case VDEF_TOTAL:
5180     case VDEF_STDEV:
5181     case VDEF_AVERAGE:{
5182         int       cnt = 0;
5183         double    sum = 0.0;
5184         double    average = 0.0;
5185
5186         for (step = 0; step < steps; step++) {
5187             if (finite(data[step * src->ds_cnt])) {
5188                 sum += data[step * src->ds_cnt];
5189                 cnt++;
5190             };
5191         }
5192         if (cnt) {
5193             if (dst->vf.op == VDEF_TOTAL) {
5194                 dst->vf.val = sum * src->step;
5195                 dst->vf.when = 0;   /* no time component */
5196                 dst->vf.never = 1;
5197             } else if (dst->vf.op == VDEF_AVERAGE) {
5198                 dst->vf.val = sum / cnt;
5199                 dst->vf.when = 0;   /* no time component */
5200                 dst->vf.never = 1;
5201             } else {
5202                 average = sum / cnt;
5203                 sum = 0.0;
5204                 for (step = 0; step < steps; step++) {
5205                     if (finite(data[step * src->ds_cnt])) {
5206                         sum += pow((data[step * src->ds_cnt] - average), 2.0);
5207                     };
5208                 }
5209                 dst->vf.val = pow(sum / cnt, 0.5);
5210                 dst->vf.when = 0;   /* no time component */
5211                 dst->vf.never = 1;
5212             };
5213         } else {
5214             dst->vf.val = DNAN;
5215             dst->vf.when = 0;
5216             dst->vf.never = 1;
5217         }
5218     }
5219         break;
5220     case VDEF_MINIMUM:
5221         step = 0;
5222         while (step != steps && isnan(data[step * src->ds_cnt]))
5223             step++;
5224         if (step == steps) {
5225             dst->vf.val = DNAN;
5226             dst->vf.when = 0;
5227             dst->vf.never = 1;
5228         } else {
5229             dst->vf.val = data[step * src->ds_cnt];
5230             dst->vf.when = src->start + (step + 1) * src->step;
5231             dst->vf.never = 0;
5232         }
5233         while (step != steps) {
5234             if (finite(data[step * src->ds_cnt])) {
5235                 if (data[step * src->ds_cnt] < dst->vf.val) {
5236                     dst->vf.val = data[step * src->ds_cnt];
5237                     dst->vf.when = src->start + (step + 1) * src->step;
5238                     dst->vf.never = 0;
5239                 }
5240             }
5241             step++;
5242         }
5243         break;
5244     case VDEF_FIRST:
5245         /* The time value returned here is one step before the
5246          * actual time value.  This is the start of the first
5247          * non-NaN interval.
5248          */
5249         step = 0;
5250         while (step != steps && isnan(data[step * src->ds_cnt]))
5251             step++;
5252         if (step == steps) {    /* all entries were NaN */
5253             dst->vf.val = DNAN;
5254             dst->vf.when = 0;
5255             dst->vf.never = 1;
5256         } else {
5257             dst->vf.val = data[step * src->ds_cnt];
5258             dst->vf.when = src->start + step * src->step;
5259             dst->vf.never = 0;
5260         }
5261         break;
5262     case VDEF_LAST:
5263         /* The time value returned here is the
5264          * actual time value.  This is the end of the last
5265          * non-NaN interval.
5266          */
5267         step = steps - 1;
5268         while (step >= 0 && isnan(data[step * src->ds_cnt]))
5269             step--;
5270         if (step < 0) { /* all entries were NaN */
5271             dst->vf.val = DNAN;
5272             dst->vf.when = 0;
5273             dst->vf.never = 1;
5274         } else {
5275             dst->vf.val = data[step * src->ds_cnt];
5276             dst->vf.when = src->start + (step + 1) * src->step;
5277             dst->vf.never = 0;
5278         }
5279         break;
5280     case VDEF_LSLSLOPE:
5281     case VDEF_LSLINT:
5282     case VDEF_LSLCORREL:{
5283         /* Bestfit line by linear least squares method */
5284
5285         int       cnt = 0;
5286         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5287
5288         SUMx = 0;
5289         SUMy = 0;
5290         SUMxy = 0;
5291         SUMxx = 0;
5292         SUMyy = 0;
5293         for (step = 0; step < steps; step++) {
5294             if (finite(data[step * src->ds_cnt])) {
5295                 cnt++;
5296                 SUMx += step;
5297                 SUMxx += step * step;
5298                 SUMxy += step * data[step * src->ds_cnt];
5299                 SUMy += data[step * src->ds_cnt];
5300                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5301             };
5302         }
5303
5304         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5305         y_intercept = (SUMy - slope * SUMx) / cnt;
5306         correl =
5307             (SUMxy -
5308              (SUMx * SUMy) / cnt) /
5309             sqrt((SUMxx -
5310                   (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5311         if (cnt) {
5312             if (dst->vf.op == VDEF_LSLSLOPE) {
5313                 dst->vf.val = slope;
5314                 dst->vf.when = 0;
5315                 dst->vf.never = 1;
5316             } else if (dst->vf.op == VDEF_LSLINT) {
5317                 dst->vf.val = y_intercept;
5318                 dst->vf.when = 0;
5319                 dst->vf.never = 1;
5320             } else if (dst->vf.op == VDEF_LSLCORREL) {
5321                 dst->vf.val = correl;
5322                 dst->vf.when = 0;
5323                 dst->vf.never = 1;
5324             };
5325         } else {
5326             dst->vf.val = DNAN;
5327             dst->vf.when = 0;
5328             dst->vf.never = 1;
5329         }
5330     }
5331         break;
5332     }
5333     return 0;
5334 }
5335
5336 /* NaN < -INF < finite_values < INF */
5337 int vdef_percent_compar(
5338     const void
5339     *a,
5340     const void
5341     *b)
5342 {
5343     /* Equality is not returned; this doesn't hurt except
5344      * (maybe) for a little performance.
5345      */
5346
5347     /* First catch NaN values. They are smallest */
5348     if (isnan(*(double *) a))
5349         return -1;
5350     if (isnan(*(double *) b))
5351         return 1;
5352     /* NaN doesn't reach this part so INF and -INF are extremes.
5353      * The sign from isinf() is compatible with the sign we return
5354      */
5355     if (isinf(*(double *) a))
5356         return isinf(*(double *) a);
5357     if (isinf(*(double *) b))
5358         return isinf(*(double *) b);
5359     /* If we reach this, both values must be finite */
5360     if (*(double *) a < *(double *) b)
5361         return -1;
5362     else
5363         return 1;
5364 }
5365
5366 void grinfo_push(
5367     image_desc_t *im,
5368     char *key,
5369     rrd_info_type_t type,
5370     rrd_infoval_t value)
5371 {
5372     im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5373     if (im->grinfo == NULL) {
5374         im->grinfo = im->grinfo_current;
5375     }
5376 }
5377
5378
5379 void time_clean(
5380     char *result,
5381     char *format)
5382 {
5383     int       j, jj;
5384     
5385 /*     Handling based on
5386        - ANSI C99 Specifications                         http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
5387        - Single UNIX Specification version 2             http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html 
5388        - POSIX:2001/Single UNIX Specification version 3  http://www.opengroup.org/onlinepubs/009695399/functions/strftime.html
5389        - POSIX:2008 Specifications                       http://www.opengroup.org/onlinepubs/9699919799/functions/strftime.html
5390        Specifications tells 
5391        "If a conversion specifier is not one of the above, the behavior is undefined."
5392
5393       C99 tells
5394        "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.
5395
5396       POSIX:2001 tells
5397       "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."
5398
5399       POSIX:2008 introduce more complexe behavior that are not handled here.
5400
5401       According to this, this code will replace:
5402       - % followed by @ by a %@
5403       - % followed by   by a %SPACE
5404       - % followed by . by a %.
5405       - % followed by % by a %
5406       - % followed by t by a TAB
5407       - % followed by E then anything by '-'
5408       - % followed by O then anything by '-'
5409       - % followed by anything else by at least one '-'. More characters may be added to better fit expected output length
5410 */
5411
5412     jj = 0;
5413     for(j = 0; (j < FMT_LEG_LEN - 1) && (jj < FMT_LEG_LEN); j++) { /* we don't need to parse the last char */
5414         if (format[j] == '%') {
5415             if ((format[j+1] == 'E') || (format[j+1] == 'O')) {
5416                 result[jj++] = '-';
5417                 j+=2; /* We skip next 2 following char */
5418             } else if ((format[j+1] == 'C') || (format[j+1] == 'd') ||
5419                        (format[j+1] == 'g') || (format[j+1] == 'H') ||
5420                        (format[j+1] == 'I') || (format[j+1] == 'm') ||
5421                        (format[j+1] == 'M') || (format[j+1] == 'S') ||
5422                        (format[j+1] == 'U') || (format[j+1] == 'V') ||
5423                        (format[j+1] == 'W') || (format[j+1] == 'y')) {
5424                 result[jj++] = '-';
5425                 if (jj < FMT_LEG_LEN) {
5426                     result[jj++] = '-';
5427                 }
5428                 j++; /* We skip the following char */
5429             } else if (format[j+1] == 'j') {
5430                 result[jj++] = '-';
5431                 if (jj < FMT_LEG_LEN - 1) {
5432                     result[jj++] = '-';
5433                     result[jj++] = '-';
5434                }
5435                 j++; /* We skip the following char */
5436             } else if ((format[j+1] == 'G') || (format[j+1] == 'Y')) {
5437                 /* Assuming Year on 4 digit */
5438                 result[jj++] = '-';
5439                 if (jj < FMT_LEG_LEN - 2) {
5440                     result[jj++] = '-';
5441                     result[jj++] = '-';
5442                     result[jj++] = '-';
5443                 }
5444                 j++; /* We skip the following char */
5445             } else if (format[j+1] == 'R') {
5446                 result[jj++] = '-';
5447                 if (jj < FMT_LEG_LEN - 3) {
5448                     result[jj++] = '-';
5449                     result[jj++] = ':';
5450                     result[jj++] = '-';
5451                     result[jj++] = '-';
5452                 }
5453                 j++; /* We skip the following char */
5454             } else if (format[j+1] == 'T') {
5455                 result[jj++] = '-';
5456                 if (jj < FMT_LEG_LEN - 6) {
5457                     result[jj++] = '-';
5458                     result[jj++] = ':';
5459                     result[jj++] = '-';
5460                     result[jj++] = '-';
5461                     result[jj++] = ':';
5462                     result[jj++] = '-';
5463                     result[jj++] = '-';
5464                 }
5465                 j++; /* We skip the following char */
5466             } else if (format[j+1] == 'F') {
5467                 result[jj++] = '-';
5468                 if (jj < FMT_LEG_LEN - 8) {
5469                     result[jj++] = '-';
5470                     result[jj++] = '-';
5471                     result[jj++] = '-';
5472                     result[jj++] = '-';
5473                     result[jj++] = '-';
5474                     result[jj++] = '-';
5475                     result[jj++] = '-';
5476                     result[jj++] = '-';
5477                     result[jj++] = '-';
5478                 }
5479                 j++; /* We skip the following char */
5480             } else if (format[j+1] == 'D') {
5481                 result[jj++] = '-';
5482                 if (jj < FMT_LEG_LEN - 6) {
5483                     result[jj++] = '-';
5484                     result[jj++] = '/';
5485                     result[jj++] = '-';
5486                     result[jj++] = '-';
5487                     result[jj++] = '/';
5488                     result[jj++] = '-';
5489                     result[jj++] = '-';
5490                 }
5491                 j++; /* We skip the following char */
5492             } else if (format[j+1] == 'n') {
5493                 result[jj++] = '\r';
5494                 result[jj++] = '\n';
5495                 j++; /* We skip the following char */
5496             } else if (format[j+1] == 't') {
5497                 result[jj++] = '\t';
5498                 j++; /* We skip the following char */
5499             } else if (format[j+1] == '%') {
5500                 result[jj++] = '%';
5501                 j++; /* We skip the following char */
5502             } else if (format[j+1] == ' ') {
5503                 if (jj < FMT_LEG_LEN - 1) {
5504                     result[jj++] = '%';
5505                     result[jj++] = ' ';
5506                 }
5507                 j++; /* We skip the following char */
5508             } else if (format[j+1] == '.') {
5509                 if (jj < FMT_LEG_LEN - 1) {
5510                     result[jj++] = '%';
5511                     result[jj++] = '.';
5512                 }
5513                 j++; /* We skip the following char */
5514             } else if (format[j+1] == '@') {
5515                 if (jj < FMT_LEG_LEN - 1) {
5516                     result[jj++] = '%';
5517                     result[jj++] = '@';
5518                 }
5519                 j++; /* We skip the following char */
5520             } else {
5521                 result[jj++] = '-';
5522                 j++; /* We skip the following char */
5523             }
5524         } else {
5525                 result[jj++] = format[j];
5526         }
5527     }
5528     result[jj] = '\0'; /* We must force the end of the string */
5529 }