Code

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