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