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