Code

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