Code

support for dashed lines in graphs
[rrdtool.git] / 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, 4, 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 issuesm 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     pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3035     cairo_set_antialias(im->cr, im->graph_antialias);
3036     cairo_scale(im->cr, im->zoom, im->zoom);
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].col.red = 0.0;
3454     im->gdes[im->gdes_c - 1].col.green = 0.0;
3455     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3456     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3457     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3458     im->gdes[im->gdes_c - 1].format[0] = '\0';
3459     im->gdes[im->gdes_c - 1].strftm = 0;
3460     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3461     im->gdes[im->gdes_c - 1].ds = -1;
3462     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3463     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3464     im->gdes[im->gdes_c - 1].yrule = DNAN;
3465     im->gdes[im->gdes_c - 1].xrule = 0;
3466     return 0;
3469 /* copies input untill the first unescaped colon is found
3470    or until input ends. backslashes have to be escaped as well */
3471 int scan_for_col(
3472     const char *const input,
3473     int len,
3474     char *const output)
3476     int       inp, outp = 0;
3478     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3479         if (input[inp] == '\\' &&
3480             input[inp + 1] != '\0' &&
3481             (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3482             output[outp++] = input[++inp];
3483         } else {
3484             output[outp++] = input[inp];
3485         }
3486     }
3487     output[outp] = '\0';
3488     return inp;
3491 /* Some surgery done on this function, it became ridiculously big.
3492 ** Things moved:
3493 ** - initializing     now in rrd_graph_init()
3494 ** - options parsing  now in rrd_graph_options()
3495 ** - script parsing   now in rrd_graph_script()
3496 */
3497 int rrd_graph(
3498     int argc,
3499     char **argv,
3500     char ***prdata,
3501     int *xsize,
3502     int *ysize,
3503     FILE * stream,
3504     double *ymin,
3505     double *ymax)
3507     image_desc_t im;
3509     rrd_graph_init(&im);
3511     /* a dummy surface so that we can measure text sizes for placements */
3512     im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3513     im.cr = cairo_create(im.surface);
3514     im.graphhandle = stream;
3516     rrd_graph_options(argc, argv, &im);
3517     if (rrd_test_error()) {
3518         im_free(&im);
3519         return -1;
3520     }
3522     if (optind >= argc) {
3523         rrd_set_error("missing filename");
3524         return -1;
3525     }
3527     if (strlen(argv[optind]) >= MAXPATH) {
3528         rrd_set_error("filename (including path) too long");
3529         im_free(&im);
3530         return -1;
3531     }
3533     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3534     im.graphfile[MAXPATH - 1] = '\0';
3536     rrd_graph_script(argc, argv, &im, 1);
3537     if (rrd_test_error()) {
3538         im_free(&im);
3539         return -1;
3540     }
3542     /* Everything is now read and the actual work can start */
3544     (*prdata) = NULL;
3545     if (graph_paint(&im, prdata) == -1) {
3546         im_free(&im);
3547         return -1;
3548     }
3550     /* The image is generated and needs to be output.
3551      ** Also, if needed, print a line with information about the image.
3552      */
3554     *xsize = im.ximg;
3555     *ysize = im.yimg;
3556     *ymin = im.minval;
3557     *ymax = im.maxval;
3558     if (im.imginfo) {
3559         char     *filename;
3561         if (!(*prdata)) {
3562             /* maybe prdata is not allocated yet ... lets do it now */
3563             if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3564                 rrd_set_error("malloc imginfo");
3565                 return -1;
3566             };
3567         }
3568         if (((*prdata)[0] =
3569              malloc((strlen(im.imginfo) + 200 +
3570                      strlen(im.graphfile)) * sizeof(char)))
3571             == NULL) {
3572             rrd_set_error("malloc imginfo");
3573             return -1;
3574         }
3575         filename = im.graphfile + strlen(im.graphfile);
3576         while (filename > im.graphfile) {
3577             if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3578                 break;
3579             filename--;
3580         }
3582         sprintf((*prdata)[0], im.imginfo, filename,
3583                 (long) (im.zoom * im.ximg), (long) (im.zoom * im.yimg));
3584     }
3585     im_free(&im);
3586     return 0;
3589 /* a simplified version of the above that just creates the graph in memory 
3590    and returns a pointer to it. */
3592 unsigned char *rrd_graph_in_memory(
3593     int argc,
3594     char **argv,
3595     char ***prdata,
3596     int *xsize,
3597     int *ysize,
3598     double *ymin,
3599     double *ymax,
3600     size_t * img_size)
3602     image_desc_t im;
3604     rrd_graph_init(&im);
3606     /* a dummy surface so that we can measure text sizes for placements */
3607     im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3608     im.cr = cairo_create(im.surface);
3610     rrd_graph_options(argc, argv, &im);
3611     if (rrd_test_error()) {
3612         im_free(&im);
3613         return NULL;
3614     }
3616     rrd_graph_script(argc, argv, &im, 1);
3617     if (rrd_test_error()) {
3618         im_free(&im);
3619         return NULL;
3620     }
3622     /* Everything is now read and the actual work can start */
3624     /* by not assigning a name to im.graphfile data will be written to
3625        newly allocated memory on im.rendered_image ... */
3627     (*prdata) = NULL;
3628     if (graph_paint(&im, prdata) == -1) {
3629         im_free(&im);
3630         return NULL;
3631     }
3633     *xsize = im.ximg;
3634     *ysize = im.yimg;
3635     *ymin = im.minval;
3636     *ymax = im.maxval;
3637     *img_size = im.rendered_image_size;
3638     im_free(&im);
3640     return im.rendered_image;
3643 void rrd_graph_init(
3644     image_desc_t *im)
3646     unsigned int i;
3648 #ifdef HAVE_TZSET
3649     tzset();
3650 #endif
3651 #ifdef HAVE_SETLOCALE
3652     setlocale(LC_TIME, "");
3653 #ifdef HAVE_MBSTOWCS
3654     setlocale(LC_CTYPE, "");
3655 #endif
3656 #endif
3657     im->yorigin = 0;
3658     im->xorigin = 0;
3659     im->minval = 0;
3660     im->xlab_user.minsec = -1;
3661     im->ximg = 0;
3662     im->yimg = 0;
3663     im->xsize = 400;
3664     im->ysize = 100;
3665     im->rendered_image_size = 0;
3666     im->rendered_image = NULL;
3667     im->step = 0;
3668     im->ylegend[0] = '\0';
3669     im->title[0] = '\0';
3670     im->watermark[0] = '\0';
3671     im->minval = DNAN;
3672     im->maxval = DNAN;
3673     im->unitsexponent = 9999;
3674     im->unitslength = 6;
3675     im->forceleftspace = 0;
3676     im->symbol = ' ';
3677     im->viewfactor = 1.0;
3678     im->imgformat = IF_PNG;
3679     im->graphfile[0] = '\0';
3680     im->cr = NULL;
3681     im->surface = NULL;
3682     im->extra_flags = 0;
3683     im->rigid = 0;
3684     im->gridfit = 1;
3685     im->imginfo = NULL;
3686     im->lazy = 0;
3687     im->slopemode = 0;
3688     im->logarithmic = 0;
3689     im->ygridstep = DNAN;
3690     im->draw_x_grid = 1;
3691     im->draw_y_grid = 1;
3692     im->base = 1000;
3693     im->prt_c = 0;
3694     im->gdes_c = 0;
3695     im->gdes = NULL;
3696     im->grid_dash_on = 1;
3697     im->grid_dash_off = 1;
3698     im->tabwidth = 40.0;
3699     im->zoom = 1;
3700     im->font_options = cairo_font_options_create();
3701     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3703     cairo_font_options_set_hint_style(im->font_options,
3704                                       CAIRO_HINT_STYLE_FULL);
3705     cairo_font_options_set_hint_metrics(im->font_options,
3706                                         CAIRO_HINT_METRICS_ON);
3707     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3710     for (i = 0; i < DIM(graph_col); i++)
3711         im->graph_col[i] = graph_col[i];
3713 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3714     {
3715         char     *windir;
3716         char      rrd_win_default_font[1000];
3718         windir = getenv("windir");
3719         /* %windir% is something like D:\windows or C:\winnt */
3720         if (windir != NULL) {
3721             strncpy(rrd_win_default_font, windir, 500);
3722             rrd_win_default_font[500] = '\0';
3723             strcat(rrd_win_default_font, "\\fonts\\");
3724             strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3725             for (i = 0; i < DIM(text_prop); i++) {
3726                 strncpy(text_prop[i].font, rrd_win_default_font,
3727                         sizeof(text_prop[i].font) - 1);
3728                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3729             }
3730         }
3731     }
3732 #endif
3733     {
3734         char     *deffont;
3736         deffont = getenv("RRD_DEFAULT_FONT");
3737         if (deffont != NULL) {
3738             for (i = 0; i < DIM(text_prop); i++) {
3739                 strncpy(text_prop[i].font, deffont,
3740                         sizeof(text_prop[i].font) - 1);
3741                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3742             }
3743         }
3744     }
3745     for (i = 0; i < DIM(text_prop); i++) {
3746         im->text_prop[i].size = text_prop[i].size;
3747         strcpy(im->text_prop[i].font, text_prop[i].font);
3748     }
3751 void rrd_graph_options(
3752     int argc,
3753     char *argv[],
3754     image_desc_t *im)
3756     int       stroff;
3757     char     *parsetime_error = NULL;
3758     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3759     time_t    start_tmp = 0, end_tmp = 0;
3760     long      long_tmp;
3761     struct rrd_time_value start_tv, end_tv;
3762     long unsigned int color;
3763     char     *old_locale = "";
3765     /* defines for long options without a short equivalent. should be bytes,
3766        and may not collide with (the ASCII value of) short options */
3767 #define LONGOPT_UNITS_SI 255
3768     struct option long_options[] = {
3769         {"start", required_argument, 0, 's'},
3770         {"end", required_argument, 0, 'e'},
3771         {"x-grid", required_argument, 0, 'x'},
3772         {"y-grid", required_argument, 0, 'y'},
3773         {"vertical-label", required_argument, 0, 'v'},
3774         {"width", required_argument, 0, 'w'},
3775         {"height", required_argument, 0, 'h'},
3776         {"full-size-mode", no_argument, 0, 'D'},
3777         {"interlaced", no_argument, 0, 'i'},
3778         {"upper-limit", required_argument, 0, 'u'},
3779         {"lower-limit", required_argument, 0, 'l'},
3780         {"rigid", no_argument, 0, 'r'},
3781         {"base", required_argument, 0, 'b'},
3782         {"logarithmic", no_argument, 0, 'o'},
3783         {"color", required_argument, 0, 'c'},
3784         {"font", required_argument, 0, 'n'},
3785         {"title", required_argument, 0, 't'},
3786         {"imginfo", required_argument, 0, 'f'},
3787         {"imgformat", required_argument, 0, 'a'},
3788         {"lazy", no_argument, 0, 'z'},
3789         {"zoom", required_argument, 0, 'm'},
3790         {"no-legend", no_argument, 0, 'g'},
3791         {"force-rules-legend", no_argument, 0, 'F'},
3792         {"only-graph", no_argument, 0, 'j'},
3793         {"alt-y-grid", no_argument, 0, 'Y'},
3794         {"no-minor", no_argument, 0, 'I'},
3795         {"slope-mode", no_argument, 0, 'E'},
3796         {"alt-autoscale", no_argument, 0, 'A'},
3797         {"alt-autoscale-min", no_argument, 0, 'J'},
3798         {"alt-autoscale-max", no_argument, 0, 'M'},
3799         {"no-gridfit", no_argument, 0, 'N'},
3800         {"units-exponent", required_argument, 0, 'X'},
3801         {"units-length", required_argument, 0, 'L'},
3802         {"units", required_argument, 0, LONGOPT_UNITS_SI},
3803         {"step", required_argument, 0, 'S'},
3804         {"tabwidth", required_argument, 0, 'T'},
3805         {"font-render-mode", required_argument, 0, 'R'},
3806         {"graph-render-mode", required_argument, 0, 'G'},
3807         {"font-smoothing-threshold", required_argument, 0, 'B'},
3808         {"watermark", required_argument, 0, 'W'},
3809         {"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 */
3810         {0, 0, 0, 0}
3811     };
3813     optind = 0;
3814     opterr = 0;         /* initialize getopt */
3816     parsetime("end-24h", &start_tv);
3817     parsetime("now", &end_tv);
3819     while (1) {
3820         int       option_index = 0;
3821         int       opt;
3822         int       col_start, col_end;
3824         opt = getopt_long(argc, argv,
3825                           "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:",
3826                           long_options, &option_index);
3828         if (opt == EOF)
3829             break;
3831         switch (opt) {
3832         case 'I':
3833             im->extra_flags |= NOMINOR;
3834             break;
3835         case 'Y':
3836             im->extra_flags |= ALTYGRID;
3837             break;
3838         case 'A':
3839             im->extra_flags |= ALTAUTOSCALE;
3840             break;
3841         case 'J':
3842             im->extra_flags |= ALTAUTOSCALE_MIN;
3843             break;
3844         case 'M':
3845             im->extra_flags |= ALTAUTOSCALE_MAX;
3846             break;
3847         case 'j':
3848             im->extra_flags |= ONLY_GRAPH;
3849             break;
3850         case 'g':
3851             im->extra_flags |= NOLEGEND;
3852             break;
3853         case 'F':
3854             im->extra_flags |= FORCE_RULES_LEGEND;
3855             break;
3856         case LONGOPT_UNITS_SI:
3857             if (im->extra_flags & FORCE_UNITS) {
3858                 rrd_set_error("--units can only be used once!");
3859                 setlocale(LC_NUMERIC, old_locale);
3860                 return;
3861             }
3862             if (strcmp(optarg, "si") == 0)
3863                 im->extra_flags |= FORCE_UNITS_SI;
3864             else {
3865                 rrd_set_error("invalid argument for --units: %s", optarg);
3866                 return;
3867             }
3868             break;
3869         case 'X':
3870             im->unitsexponent = atoi(optarg);
3871             break;
3872         case 'L':
3873             im->unitslength = atoi(optarg);
3874             im->forceleftspace = 1;
3875             break;
3876         case 'T':
3877             old_locale = setlocale(LC_NUMERIC, "C");
3878             im->tabwidth = atof(optarg);
3879             setlocale(LC_NUMERIC, old_locale);
3880             break;
3881         case 'S':
3882             old_locale = setlocale(LC_NUMERIC, "C");
3883             im->step = atoi(optarg);
3884             setlocale(LC_NUMERIC, old_locale);
3885             break;
3886         case 'N':
3887             im->gridfit = 0;
3888             break;
3889         case 's':
3890             if ((parsetime_error = parsetime(optarg, &start_tv))) {
3891                 rrd_set_error("start time: %s", parsetime_error);
3892                 return;
3893             }
3894             break;
3895         case 'e':
3896             if ((parsetime_error = parsetime(optarg, &end_tv))) {
3897                 rrd_set_error("end time: %s", parsetime_error);
3898                 return;
3899             }
3900             break;
3901         case 'x':
3902             if (strcmp(optarg, "none") == 0) {
3903                 im->draw_x_grid = 0;
3904                 break;
3905             };
3907             if (sscanf(optarg,
3908                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3909                        scan_gtm,
3910                        &im->xlab_user.gridst,
3911                        scan_mtm,
3912                        &im->xlab_user.mgridst,
3913                        scan_ltm,
3914                        &im->xlab_user.labst,
3915                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3916                 strncpy(im->xlab_form, optarg + stroff,
3917                         sizeof(im->xlab_form) - 1);
3918                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3919                 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3920                     rrd_set_error("unknown keyword %s", scan_gtm);
3921                     return;
3922                 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3923                            == -1) {
3924                     rrd_set_error("unknown keyword %s", scan_mtm);
3925                     return;
3926                 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3927                            -1) {
3928                     rrd_set_error("unknown keyword %s", scan_ltm);
3929                     return;
3930                 }
3931                 im->xlab_user.minsec = 1;
3932                 im->xlab_user.stst = im->xlab_form;
3933             } else {
3934                 rrd_set_error("invalid x-grid format");
3935                 return;
3936             }
3937             break;
3938         case 'y':
3940             if (strcmp(optarg, "none") == 0) {
3941                 im->draw_y_grid = 0;
3942                 break;
3943             };
3944             old_locale = setlocale(LC_NUMERIC, "C");
3945             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3946                 setlocale(LC_NUMERIC, old_locale);
3947                 if (im->ygridstep <= 0) {
3948                     rrd_set_error("grid step must be > 0");
3949                     return;
3950                 } else if (im->ylabfact < 1) {
3951                     rrd_set_error("label factor must be > 0");
3952                     return;
3953                 }
3954             } else {
3955                 setlocale(LC_NUMERIC, old_locale);
3956                 rrd_set_error("invalid y-grid format");
3957                 return;
3958             }
3959             break;
3960         case 'v':
3961             strncpy(im->ylegend, optarg, 150);
3962             im->ylegend[150] = '\0';
3963             break;
3964         case 'u':
3965             old_locale = setlocale(LC_NUMERIC, "C");
3966             im->maxval = atof(optarg);
3967             setlocale(LC_NUMERIC, old_locale);
3968             break;
3969         case 'l':
3970             old_locale = setlocale(LC_NUMERIC, "C");
3971             im->minval = atof(optarg);
3972             setlocale(LC_NUMERIC, old_locale);
3973             break;
3974         case 'b':
3975             im->base = atol(optarg);
3976             if (im->base != 1024 && im->base != 1000) {
3977                 rrd_set_error
3978                     ("the only sensible value for base apart from 1000 is 1024");
3979                 return;
3980             }
3981             break;
3982         case 'w':
3983             long_tmp = atol(optarg);
3984             if (long_tmp < 10) {
3985                 rrd_set_error("width below 10 pixels");
3986                 return;
3987             }
3988             im->xsize = long_tmp;
3989             break;
3990         case 'h':
3991             long_tmp = atol(optarg);
3992             if (long_tmp < 10) {
3993                 rrd_set_error("height below 10 pixels");
3994                 return;
3995             }
3996             im->ysize = long_tmp;
3997             break;
3998         case 'D':
3999             im->extra_flags |= FULL_SIZE_MODE;
4000             break;
4001         case 'i':
4002             /* interlaced png not supported at the moment */
4003             break;
4004         case 'r':
4005             im->rigid = 1;
4006             break;
4007         case 'f':
4008             im->imginfo = optarg;
4009             break;
4010         case 'a':
4011             if ((int) (im->imgformat = if_conv(optarg)) == -1) {
4012                 rrd_set_error("unsupported graphics format '%s'", optarg);
4013                 return;
4014             }
4015             break;
4016         case 'z':
4017             im->lazy = 1;
4018             break;
4019         case 'E':
4020             im->slopemode = 1;
4021             break;
4023         case 'o':
4024             im->logarithmic = 1;
4025             break;
4026         case 'c':
4027             if (sscanf(optarg,
4028                        "%10[A-Z]#%n%8lx%n",
4029                        col_nam, &col_start, &color, &col_end) == 2) {
4030                 int       ci;
4031                 int       col_len = col_end - col_start;
4033                 switch (col_len) {
4034                 case 3:
4035                     color = (((color & 0xF00) * 0x110000) |
4036                              ((color & 0x0F0) * 0x011000) |
4037                              ((color & 0x00F) * 0x001100) | 0x000000FF);
4038                     break;
4039                 case 4:
4040                     color = (((color & 0xF000) * 0x11000) |
4041                              ((color & 0x0F00) * 0x01100) |
4042                              ((color & 0x00F0) * 0x00110) |
4043                              ((color & 0x000F) * 0x00011)
4044                         );
4045                     break;
4046                 case 6:
4047                     color = (color << 8) + 0xff /* shift left by 8 */ ;
4048                     break;
4049                 case 8:
4050                     break;
4051                 default:
4052                     rrd_set_error("the color format is #RRGGBB[AA]");
4053                     return;
4054                 }
4055                 if ((ci = grc_conv(col_nam)) != -1) {
4056                     im->graph_col[ci] = gfx_hex_to_col(color);
4057                 } else {
4058                     rrd_set_error("invalid color name '%s'", col_nam);
4059                     return;
4060                 }
4061             } else {
4062                 rrd_set_error("invalid color def format");
4063                 return;
4064             }
4065             break;
4066         case 'n':{
4067             char      prop[15];
4068             double    size = 1;
4069             char      font[1024] = "";
4071             old_locale = setlocale(LC_NUMERIC, "C");
4072             if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
4073                 int       sindex, propidx;
4075                 setlocale(LC_NUMERIC, old_locale);
4076                 if ((sindex = text_prop_conv(prop)) != -1) {
4077                     for (propidx = sindex; propidx < TEXT_PROP_LAST;
4078                          propidx++) {
4079                         if (size > 0) {
4080                             im->text_prop[propidx].size = size;
4081                         }
4082                         if (strlen(font) > 0) {
4083                             strcpy(im->text_prop[propidx].font, font);
4084                         }
4085                         if (propidx == sindex && sindex != 0)
4086                             break;
4087                     }
4088                 } else {
4089                     rrd_set_error("invalid fonttag '%s'", prop);
4090                     return;
4091                 }
4092             } else {
4093                 setlocale(LC_NUMERIC, old_locale);
4094                 rrd_set_error("invalid text property format");
4095                 return;
4096             }
4097             break;
4098         }
4099         case 'm':
4100             old_locale = setlocale(LC_NUMERIC, "C");
4101             im->zoom = atof(optarg);
4102             setlocale(LC_NUMERIC, old_locale);
4103             if (im->zoom <= 0.0) {
4104                 rrd_set_error("zoom factor must be > 0");
4105                 return;
4106             }
4107             break;
4108         case 't':
4109             strncpy(im->title, optarg, 150);
4110             im->title[150] = '\0';
4111             break;
4113         case 'R':
4114             if (strcmp(optarg, "normal") == 0) {
4115                 cairo_font_options_set_antialias(im->font_options,
4116                                                  CAIRO_ANTIALIAS_GRAY);
4117                 cairo_font_options_set_hint_style(im->font_options,
4118                                                   CAIRO_HINT_STYLE_FULL);
4119             } else if (strcmp(optarg, "light") == 0) {
4120                 cairo_font_options_set_antialias(im->font_options,
4121                                                  CAIRO_ANTIALIAS_GRAY);
4122                 cairo_font_options_set_hint_style(im->font_options,
4123                                                   CAIRO_HINT_STYLE_SLIGHT);
4124             } else if (strcmp(optarg, "mono") == 0) {
4125                 cairo_font_options_set_antialias(im->font_options,
4126                                                  CAIRO_ANTIALIAS_NONE);
4127                 cairo_font_options_set_hint_style(im->font_options,
4128                                                   CAIRO_HINT_STYLE_FULL);
4129             } else {
4130                 rrd_set_error("unknown font-render-mode '%s'", optarg);
4131                 return;
4132             }
4133             break;
4134         case 'G':
4135             if (strcmp(optarg, "normal") == 0)
4136                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4137             else if (strcmp(optarg, "mono") == 0)
4138                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4139             else {
4140                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4141                 return;
4142             }
4143             break;
4144         case 'B':
4145             /* not supported curently */
4146             break;
4148         case 'W':
4149             strncpy(im->watermark, optarg, 100);
4150             im->watermark[99] = '\0';
4151             break;
4153         case '?':
4154             if (optopt != 0)
4155                 rrd_set_error("unknown option '%c'", optopt);
4156             else
4157                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4158             return;
4159         }
4160     }
4162     if (im->logarithmic == 1 && im->minval <= 0) {
4163         rrd_set_error
4164             ("for a logarithmic yaxis you must specify a lower-limit > 0");
4165         return;
4166     }
4168     if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4169         /* error string is set in parsetime.c */
4170         return;
4171     }
4173     if (start_tmp < 3600 * 24 * 365 * 10) {
4174         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
4175                       start_tmp);
4176         return;
4177     }
4179     if (end_tmp < start_tmp) {
4180         rrd_set_error("start (%ld) should be less than end (%ld)",
4181                       start_tmp, end_tmp);
4182         return;
4183     }
4185     im->start = start_tmp;
4186     im->end = end_tmp;
4187     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4190 int rrd_graph_color(
4191     image_desc_t *im,
4192     char *var,
4193     char *err,
4194     int optional)
4196     char     *color;
4197     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4199     color = strstr(var, "#");
4200     if (color == NULL) {
4201         if (optional == 0) {
4202             rrd_set_error("Found no color in %s", err);
4203             return 0;
4204         }
4205         return 0;
4206     } else {
4207         int       n = 0;
4208         char     *rest;
4209         long unsigned int col;
4211         rest = strstr(color, ":");
4212         if (rest != NULL)
4213             n = rest - color;
4214         else
4215             n = strlen(color);
4217         switch (n) {
4218         case 7:
4219             sscanf(color, "#%6lx%n", &col, &n);
4220             col = (col << 8) + 0xff /* shift left by 8 */ ;
4221             if (n != 7)
4222                 rrd_set_error("Color problem in %s", err);
4223             break;
4224         case 9:
4225             sscanf(color, "#%8lx%n", &col, &n);
4226             if (n == 9)
4227                 break;
4228         default:
4229             rrd_set_error("Color problem in %s", err);
4230         }
4231         if (rrd_test_error())
4232             return 0;
4233         gdp->col = gfx_hex_to_col(col);
4234         return n;
4235     }
4239 int bad_format(
4240     char *fmt)
4242     char     *ptr;
4243     int       n = 0;
4245     ptr = fmt;
4246     while (*ptr != '\0')
4247         if (*ptr++ == '%') {
4249             /* line cannot end with percent char */
4250             if (*ptr == '\0')
4251                 return 1;
4253             /* '%s', '%S' and '%%' are allowed */
4254             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4255                 ptr++;
4257             /* %c is allowed (but use only with vdef!) */
4258             else if (*ptr == 'c') {
4259                 ptr++;
4260                 n = 1;
4261             }
4263             /* or else '% 6.2lf' and such are allowed */
4264             else {
4265                 /* optional padding character */
4266                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4267                     ptr++;
4269                 /* This should take care of 'm.n' with all three optional */
4270                 while (*ptr >= '0' && *ptr <= '9')
4271                     ptr++;
4272                 if (*ptr == '.')
4273                     ptr++;
4274                 while (*ptr >= '0' && *ptr <= '9')
4275                     ptr++;
4277                 /* Either 'le', 'lf' or 'lg' must follow here */
4278                 if (*ptr++ != 'l')
4279                     return 1;
4280                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4281                     ptr++;
4282                 else
4283                     return 1;
4284                 n++;
4285             }
4286         }
4288     return (n != 1);
4292 int vdef_parse(
4293     struct graph_desc_t *gdes,
4294     const char *const str)
4296     /* A VDEF currently is either "func" or "param,func"
4297      * so the parsing is rather simple.  Change if needed.
4298      */
4299     double    param;
4300     char      func[30];
4301     int       n;
4302     char     *old_locale;
4304     n = 0;
4305     old_locale = setlocale(LC_NUMERIC, "C");
4306     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4307     setlocale(LC_NUMERIC, old_locale);
4308     if (n == (int) strlen(str)) {   /* matched */
4309         ;
4310     } else {
4311         n = 0;
4312         sscanf(str, "%29[A-Z]%n", func, &n);
4313         if (n == (int) strlen(str)) {   /* matched */
4314             param = DNAN;
4315         } else {
4316             rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4317                           gdes->vname);
4318             return -1;
4319         }
4320     }
4321     if (!strcmp("PERCENT", func))
4322         gdes->vf.op = VDEF_PERCENT;
4323     else if (!strcmp("MAXIMUM", func))
4324         gdes->vf.op = VDEF_MAXIMUM;
4325     else if (!strcmp("AVERAGE", func))
4326         gdes->vf.op = VDEF_AVERAGE;
4327     else if (!strcmp("STDEV", func))
4328         gdes->vf.op = VDEF_STDEV;
4329     else if (!strcmp("MINIMUM", func))
4330         gdes->vf.op = VDEF_MINIMUM;
4331     else if (!strcmp("TOTAL", func))
4332         gdes->vf.op = VDEF_TOTAL;
4333     else if (!strcmp("FIRST", func))
4334         gdes->vf.op = VDEF_FIRST;
4335     else if (!strcmp("LAST", func))
4336         gdes->vf.op = VDEF_LAST;
4337     else if (!strcmp("LSLSLOPE", func))
4338         gdes->vf.op = VDEF_LSLSLOPE;
4339     else if (!strcmp("LSLINT", func))
4340         gdes->vf.op = VDEF_LSLINT;
4341     else if (!strcmp("LSLCORREL", func))
4342         gdes->vf.op = VDEF_LSLCORREL;
4343     else {
4344         rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4345                       gdes->vname);
4346         return -1;
4347     };
4349     switch (gdes->vf.op) {
4350     case VDEF_PERCENT:
4351         if (isnan(param)) { /* no parameter given */
4352             rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4353                           func, gdes->vname);
4354             return -1;
4355         };
4356         if (param >= 0.0 && param <= 100.0) {
4357             gdes->vf.param = param;
4358             gdes->vf.val = DNAN;    /* undefined */
4359             gdes->vf.when = 0;  /* undefined */
4360         } else {
4361             rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4362                           gdes->vname);
4363             return -1;
4364         };
4365         break;
4366     case VDEF_MAXIMUM:
4367     case VDEF_AVERAGE:
4368     case VDEF_STDEV:
4369     case VDEF_MINIMUM:
4370     case VDEF_TOTAL:
4371     case VDEF_FIRST:
4372     case VDEF_LAST:
4373     case VDEF_LSLSLOPE:
4374     case VDEF_LSLINT:
4375     case VDEF_LSLCORREL:
4376         if (isnan(param)) {
4377             gdes->vf.param = DNAN;
4378             gdes->vf.val = DNAN;
4379             gdes->vf.when = 0;
4380         } else {
4381             rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4382                           func, gdes->vname);
4383             return -1;
4384         };
4385         break;
4386     };
4387     return 0;
4391 int vdef_calc(
4392     image_desc_t *im,
4393     int gdi)
4395     graph_desc_t *src, *dst;
4396     rrd_value_t *data;
4397     long      step, steps;
4399     dst = &im->gdes[gdi];
4400     src = &im->gdes[dst->vidx];
4401     data = src->data + src->ds;
4402     steps = (src->end - src->start) / src->step;
4404 #if 0
4405     printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4406            src->end, steps);
4407 #endif
4409     switch (dst->vf.op) {
4410     case VDEF_PERCENT:{
4411         rrd_value_t *array;
4412         int       field;
4415         if ((array = malloc(steps * sizeof(double))) == NULL) {
4416             rrd_set_error("malloc VDEV_PERCENT");
4417             return -1;
4418         }
4419         for (step = 0; step < steps; step++) {
4420             array[step] = data[step * src->ds_cnt];
4421         }
4422         qsort(array, step, sizeof(double), vdef_percent_compar);
4424         field = (steps - 1) * dst->vf.param / 100;
4425         dst->vf.val = array[field];
4426         dst->vf.when = 0;   /* no time component */
4427         free(array);
4428 #if 0
4429         for (step = 0; step < steps; step++)
4430             printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4431                    step == field ? '*' : ' ');
4432 #endif
4433     }
4434         break;
4435     case VDEF_MAXIMUM:
4436         step = 0;
4437         while (step != steps && isnan(data[step * src->ds_cnt]))
4438             step++;
4439         if (step == steps) {
4440             dst->vf.val = DNAN;
4441             dst->vf.when = 0;
4442         } else {
4443             dst->vf.val = data[step * src->ds_cnt];
4444             dst->vf.when = src->start + (step + 1) * src->step;
4445         }
4446         while (step != steps) {
4447             if (finite(data[step * src->ds_cnt])) {
4448                 if (data[step * src->ds_cnt] > dst->vf.val) {
4449                     dst->vf.val = data[step * src->ds_cnt];
4450                     dst->vf.when = src->start + (step + 1) * src->step;
4451                 }
4452             }
4453             step++;
4454         }
4455         break;
4456     case VDEF_TOTAL:
4457     case VDEF_STDEV:
4458     case VDEF_AVERAGE:{
4459         int       cnt = 0;
4460         double    sum = 0.0;
4461         double    average = 0.0;
4463         for (step = 0; step < steps; step++) {
4464             if (finite(data[step * src->ds_cnt])) {
4465                 sum += data[step * src->ds_cnt];
4466                 cnt++;
4467             };
4468         }
4469         if (cnt) {
4470             if (dst->vf.op == VDEF_TOTAL) {
4471                 dst->vf.val = sum * src->step;
4472                 dst->vf.when = 0;   /* no time component */
4473             } else if (dst->vf.op == VDEF_AVERAGE) {
4474                 dst->vf.val = sum / cnt;
4475                 dst->vf.when = 0;   /* no time component */
4476             } else {
4477                 average = sum / cnt;
4478                 sum = 0.0;
4479                 for (step = 0; step < steps; step++) {
4480                     if (finite(data[step * src->ds_cnt])) {
4481                         sum += pow((data[step * src->ds_cnt] - average), 2.0);
4482                     };
4483                 }
4484                 dst->vf.val = pow(sum / cnt, 0.5);
4485                 dst->vf.when = 0;   /* no time component */
4486             };
4487         } else {
4488             dst->vf.val = DNAN;
4489             dst->vf.when = 0;
4490         }
4491     }
4492         break;
4493     case VDEF_MINIMUM:
4494         step = 0;
4495         while (step != steps && isnan(data[step * src->ds_cnt]))
4496             step++;
4497         if (step == steps) {
4498             dst->vf.val = DNAN;
4499             dst->vf.when = 0;
4500         } else {
4501             dst->vf.val = data[step * src->ds_cnt];
4502             dst->vf.when = src->start + (step + 1) * src->step;
4503         }
4504         while (step != steps) {
4505             if (finite(data[step * src->ds_cnt])) {
4506                 if (data[step * src->ds_cnt] < dst->vf.val) {
4507                     dst->vf.val = data[step * src->ds_cnt];
4508                     dst->vf.when = src->start + (step + 1) * src->step;
4509                 }
4510             }
4511             step++;
4512         }
4513         break;
4514     case VDEF_FIRST:
4515         /* The time value returned here is one step before the
4516          * actual time value.  This is the start of the first
4517          * non-NaN interval.
4518          */
4519         step = 0;
4520         while (step != steps && isnan(data[step * src->ds_cnt]))
4521             step++;
4522         if (step == steps) {    /* all entries were NaN */
4523             dst->vf.val = DNAN;
4524             dst->vf.when = 0;
4525         } else {
4526             dst->vf.val = data[step * src->ds_cnt];
4527             dst->vf.when = src->start + step * src->step;
4528         }
4529         break;
4530     case VDEF_LAST:
4531         /* The time value returned here is the
4532          * actual time value.  This is the end of the last
4533          * non-NaN interval.
4534          */
4535         step = steps - 1;
4536         while (step >= 0 && isnan(data[step * src->ds_cnt]))
4537             step--;
4538         if (step < 0) { /* all entries were NaN */
4539             dst->vf.val = DNAN;
4540             dst->vf.when = 0;
4541         } else {
4542             dst->vf.val = data[step * src->ds_cnt];
4543             dst->vf.when = src->start + (step + 1) * src->step;
4544         }
4545         break;
4546     case VDEF_LSLSLOPE:
4547     case VDEF_LSLINT:
4548     case VDEF_LSLCORREL:{
4549         /* Bestfit line by linear least squares method */
4551         int       cnt = 0;
4552         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4554         SUMx = 0;
4555         SUMy = 0;
4556         SUMxy = 0;
4557         SUMxx = 0;
4558         SUMyy = 0;
4560         for (step = 0; step < steps; step++) {
4561             if (finite(data[step * src->ds_cnt])) {
4562                 cnt++;
4563                 SUMx += step;
4564                 SUMxx += step * step;
4565                 SUMxy += step * data[step * src->ds_cnt];
4566                 SUMy += data[step * src->ds_cnt];
4567                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4568             };
4569         }
4571         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4572         y_intercept = (SUMy - slope * SUMx) / cnt;
4573         correl =
4574             (SUMxy -
4575              (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4576                                           (SUMx * SUMx) / cnt) * (SUMyy -
4577                                                                   (SUMy *
4578                                                                    SUMy) /
4579                                                                   cnt));
4581         if (cnt) {
4582             if (dst->vf.op == VDEF_LSLSLOPE) {
4583                 dst->vf.val = slope;
4584                 dst->vf.when = 0;
4585             } else if (dst->vf.op == VDEF_LSLINT) {
4586                 dst->vf.val = y_intercept;
4587                 dst->vf.when = 0;
4588             } else if (dst->vf.op == VDEF_LSLCORREL) {
4589                 dst->vf.val = correl;
4590                 dst->vf.when = 0;
4591             };
4593         } else {
4594             dst->vf.val = DNAN;
4595             dst->vf.when = 0;
4596         }
4597     }
4598         break;
4599     }
4600     return 0;
4603 /* NaN < -INF < finite_values < INF */
4604 int vdef_percent_compar(
4605     const void *a,
4606     const void *b)
4608     /* Equality is not returned; this doesn't hurt except
4609      * (maybe) for a little performance.
4610      */
4612     /* First catch NaN values. They are smallest */
4613     if (isnan(*(double *) a))
4614         return -1;
4615     if (isnan(*(double *) b))
4616         return 1;
4618     /* NaN doesn't reach this part so INF and -INF are extremes.
4619      * The sign from isinf() is compatible with the sign we return
4620      */
4621     if (isinf(*(double *) a))
4622         return isinf(*(double *) a);
4623     if (isinf(*(double *) b))
4624         return isinf(*(double *) b);
4626     /* If we reach this, both values must be finite */
4627     if (*(double *) a < *(double *) b)
4628         return -1;
4629     else
4630         return 1;