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