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