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