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