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