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