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