Code

--full-size-mode with and without --no-legend should work now -- Matthew Chambers
[rrdtool.git] / src / rrd_graph.c
1 /****************************************************************************
2  * RRDtool 1.2.23  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 */
143 { 0xFFFFFFFF,           /* canvas     */
144     0xF0F0F0FF,         /* background */
145     0xD0D0D0FF,         /* shade A    */
146     0xA0A0A0FF,         /* shade B    */
147     0x90909080,         /* grid       */
148     0xE0505080,         /* major grid */
149     0x000000FF,         /* font       */
150     0x802020FF,         /* arrow      */
151     0x202020FF,         /* axis       */
152     0x000000FF          /* frame      */
153 };
156 /* #define DEBUG */
158 #ifdef DEBUG
159 # define DPRINT(x)    (void)(printf x, printf("\n"))
160 #else
161 # define DPRINT(x)
162 #endif
165 /* initialize with xtr(im,0); */
166 int xtr(
167     image_desc_t *im,
168     time_t mytime)
170     static double pixie;
172     if (mytime == 0) {
173         pixie = (double) im->xsize / (double) (im->end - im->start);
174         return im->xorigin;
175     }
176     return (int) ((double) im->xorigin + pixie * (mytime - im->start));
179 /* translate data values into y coordinates */
180 double ytr(
181     image_desc_t *im,
182     double value)
184     static double pixie;
185     double    yval;
187     if (isnan(value)) {
188         if (!im->logarithmic)
189             pixie = (double) im->ysize / (im->maxval - im->minval);
190         else
191             pixie =
192                 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
193         yval = im->yorigin;
194     } else if (!im->logarithmic) {
195         yval = im->yorigin - pixie * (value - im->minval);
196     } else {
197         if (value < im->minval) {
198             yval = im->yorigin;
199         } else {
200             yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
201         }
202     }
203     /* make sure we don't return anything too unreasonable. GD lib can
204        get terribly slow when drawing lines outside its scope. This is 
205        especially problematic in connection with the rigid option */
206     if (!im->rigid) {
207         /* keep yval as-is */
208     } else if (yval > im->yorigin) {
209         yval = im->yorigin + 0.00001;
210     } else if (yval < im->yorigin - im->ysize) {
211         yval = im->yorigin - im->ysize - 0.00001;
212     }
213     return yval;
218 /* conversion function for symbolic entry names */
221 #define conv_if(VV,VVV) \
222    if (strcmp(#VV, string) == 0) return VVV ;
224 enum gf_en gf_conv(
225     char *string)
228     conv_if(PRINT, GF_PRINT)
229         conv_if(GPRINT, GF_GPRINT)
230         conv_if(COMMENT, GF_COMMENT)
231         conv_if(HRULE, GF_HRULE)
232         conv_if(VRULE, GF_VRULE)
233         conv_if(LINE, GF_LINE)
234         conv_if(AREA, GF_AREA)
235         conv_if(STACK, GF_STACK)
236         conv_if(TICK, GF_TICK)
237         conv_if(DEF, GF_DEF)
238         conv_if(CDEF, GF_CDEF)
239         conv_if(VDEF, GF_VDEF)
240 #ifdef WITH_PIECHART
241         conv_if(PART, GF_PART)
242 #endif
243         conv_if(XPORT, GF_XPORT)
244         conv_if(SHIFT, GF_SHIFT)
246         return (-1);
249 enum gfx_if_en if_conv(
250     char *string)
253     conv_if(PNG, IF_PNG)
254         conv_if(SVG, IF_SVG)
255         conv_if(EPS, IF_EPS)
256         conv_if(PDF, IF_PDF)
258         return (-1);
261 enum tmt_en tmt_conv(
262     char *string)
265     conv_if(SECOND, TMT_SECOND)
266         conv_if(MINUTE, TMT_MINUTE)
267         conv_if(HOUR, TMT_HOUR)
268         conv_if(DAY, TMT_DAY)
269         conv_if(WEEK, TMT_WEEK)
270         conv_if(MONTH, TMT_MONTH)
271         conv_if(YEAR, TMT_YEAR)
272         return (-1);
275 enum grc_en grc_conv(
276     char *string)
279     conv_if(BACK, GRC_BACK)
280         conv_if(CANVAS, GRC_CANVAS)
281         conv_if(SHADEA, GRC_SHADEA)
282         conv_if(SHADEB, GRC_SHADEB)
283         conv_if(GRID, GRC_GRID)
284         conv_if(MGRID, GRC_MGRID)
285         conv_if(FONT, GRC_FONT)
286         conv_if(ARROW, GRC_ARROW)
287         conv_if(AXIS, GRC_AXIS)
288         conv_if(FRAME, GRC_FRAME)
290         return -1;
293 enum text_prop_en text_prop_conv(
294     char *string)
297     conv_if(DEFAULT, TEXT_PROP_DEFAULT)
298         conv_if(TITLE, TEXT_PROP_TITLE)
299         conv_if(AXIS, TEXT_PROP_AXIS)
300         conv_if(UNIT, TEXT_PROP_UNIT)
301         conv_if(LEGEND, TEXT_PROP_LEGEND)
302         return -1;
306 #undef conv_if
308 int im_free(
309     image_desc_t *im)
311     unsigned long i, ii;
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(im->gdes[i].p_data);
326         free(im->gdes[i].rpnp);
327     }
328     free(im->gdes);
329     gfx_destroy(im->canvas);
330     return 0;
333 /* find SI magnitude symbol for the given number*/
334 void auto_scale(
335     image_desc_t *im,   /* image description */
336     double *value,
337     char **symb_ptr,
338     double *magfact)
341     char     *symbol[] = { "a", /* 10e-18 Atto */
342         "f",            /* 10e-15 Femto */
343         "p",            /* 10e-12 Pico */
344         "n",            /* 10e-9  Nano */
345         "u",            /* 10e-6  Micro */
346         "m",            /* 10e-3  Milli */
347         " ",            /* Base */
348         "k",            /* 10e3   Kilo */
349         "M",            /* 10e6   Mega */
350         "G",            /* 10e9   Giga */
351         "T",            /* 10e12  Tera */
352         "P",            /* 10e15  Peta */
353         "E"
354     };                  /* 10e18  Exa */
356     int       symbcenter = 6;
357     int       sindex;
359     if (*value == 0.0 || isnan(*value)) {
360         sindex = 0;
361         *magfact = 1.0;
362     } else {
363         sindex = floor(log(fabs(*value)) / log((double) im->base));
364         *magfact = pow((double) im->base, (double) sindex);
365         (*value) /= (*magfact);
366     }
367     if (sindex <= symbcenter && sindex >= -symbcenter) {
368         (*symb_ptr) = symbol[sindex + symbcenter];
369     } else {
370         (*symb_ptr) = "?";
371     }
375 static char si_symbol[] = {
376     'a',                /* 10e-18 Atto */
377     'f',                /* 10e-15 Femto */
378     'p',                /* 10e-12 Pico */
379     'n',                /* 10e-9  Nano */
380     'u',                /* 10e-6  Micro */
381     'm',                /* 10e-3  Milli */
382     ' ',                /* Base */
383     'k',                /* 10e3   Kilo */
384     'M',                /* 10e6   Mega */
385     'G',                /* 10e9   Giga */
386     'T',                /* 10e12  Tera */
387     'P',                /* 10e15  Peta */
388     'E',                /* 10e18  Exa */
389 };
390 static const int si_symbcenter = 6;
392 /* find SI magnitude symbol for the numbers on the y-axis*/
393 void si_unit(
394     image_desc_t *im    /* image description */
395     )
398     double    digits, viewdigits = 0;
400     digits =
401         floor(log(max(fabs(im->minval), fabs(im->maxval))) /
402               log((double) im->base));
404     if (im->unitsexponent != 9999) {
405         /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
406         viewdigits = floor(im->unitsexponent / 3);
407     } else {
408         viewdigits = digits;
409     }
411     im->magfact = pow((double) im->base, digits);
413 #ifdef DEBUG
414     printf("digits %6.3f  im->magfact %6.3f\n", digits, im->magfact);
415 #endif
417     im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
419     if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
420         ((viewdigits + si_symbcenter) >= 0))
421         im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
422     else
423         im->symbol = '?';
426 /*  move min and max values around to become sensible */
428 void expand_range(
429     image_desc_t *im)
431     double    sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
432         600.0, 500.0, 400.0, 300.0, 250.0,
433         200.0, 125.0, 100.0, 90.0, 80.0,
434         75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
435         25.0, 20.0, 10.0, 9.0, 8.0,
436         7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
437         2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
438         0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
439     };
441     double    scaled_min, scaled_max;
442     double    adj;
443     int       i;
447 #ifdef DEBUG
448     printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
449            im->minval, im->maxval, im->magfact);
450 #endif
452     if (isnan(im->ygridstep)) {
453         if (im->extra_flags & ALTAUTOSCALE) {
454             /* measure the amplitude of the function. Make sure that
455                graph boundaries are slightly higher then max/min vals
456                so we can see amplitude on the graph */
457             double    delt, fact;
459             delt = im->maxval - im->minval;
460             adj = delt * 0.1;
461             fact = 2.0 * pow(10.0,
462                              floor(log10
463                                    (max(fabs(im->minval), fabs(im->maxval)) /
464                                     im->magfact)) - 2);
465             if (delt < fact) {
466                 adj = (fact - delt) * 0.55;
467 #ifdef DEBUG
468                 printf
469                     ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
470                      im->minval, im->maxval, delt, fact, adj);
471 #endif
472             }
473             im->minval -= adj;
474             im->maxval += adj;
475         } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
476             /* measure the amplitude of the function. Make sure that
477                graph boundaries are slightly lower than min vals
478                so we can see amplitude on the graph */
479             adj = (im->maxval - im->minval) * 0.1;
480             im->minval -= adj;
481         } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
482             /* measure the amplitude of the function. Make sure that
483                graph boundaries are slightly higher than max vals
484                so we can see amplitude on the graph */
485             adj = (im->maxval - im->minval) * 0.1;
486             im->maxval += adj;
487         } else {
488             scaled_min = im->minval / im->magfact;
489             scaled_max = im->maxval / im->magfact;
491             for (i = 1; sensiblevalues[i] > 0; i++) {
492                 if (sensiblevalues[i - 1] >= scaled_min &&
493                     sensiblevalues[i] <= scaled_min)
494                     im->minval = sensiblevalues[i] * (im->magfact);
496                 if (-sensiblevalues[i - 1] <= scaled_min &&
497                     -sensiblevalues[i] >= scaled_min)
498                     im->minval = -sensiblevalues[i - 1] * (im->magfact);
500                 if (sensiblevalues[i - 1] >= scaled_max &&
501                     sensiblevalues[i] <= scaled_max)
502                     im->maxval = sensiblevalues[i - 1] * (im->magfact);
504                 if (-sensiblevalues[i - 1] <= scaled_max &&
505                     -sensiblevalues[i] >= scaled_max)
506                     im->maxval = -sensiblevalues[i] * (im->magfact);
507             }
508         }
509     } else {
510         /* adjust min and max to the grid definition if there is one */
511         im->minval = (double) im->ylabfact * im->ygridstep *
512             floor(im->minval / ((double) im->ylabfact * im->ygridstep));
513         im->maxval = (double) im->ylabfact * im->ygridstep *
514             ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
515     }
517 #ifdef DEBUG
518     fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
519             im->minval, im->maxval, im->magfact);
520 #endif
523 void apply_gridfit(
524     image_desc_t *im)
526     if (isnan(im->minval) || isnan(im->maxval))
527         return;
528     ytr(im, DNAN);
529     if (im->logarithmic) {
530         double    ya, yb, ypix, ypixfrac;
531         double    log10_range = log10(im->maxval) - log10(im->minval);
533         ya = pow((double) 10, floor(log10(im->minval)));
534         while (ya < im->minval)
535             ya *= 10;
536         if (ya > im->maxval)
537             return;     /* don't have y=10^x gridline */
538         yb = ya * 10;
539         if (yb <= im->maxval) {
540             /* we have at least 2 y=10^x gridlines.
541                Make sure distance between them in pixels
542                are an integer by expanding im->maxval */
543             double    y_pixel_delta = ytr(im, ya) - ytr(im, yb);
544             double    factor = y_pixel_delta / floor(y_pixel_delta);
545             double    new_log10_range = factor * log10_range;
546             double    new_ymax_log10 = log10(im->minval) + new_log10_range;
548             im->maxval = pow(10, new_ymax_log10);
549             ytr(im, DNAN);  /* reset precalc */
550             log10_range = log10(im->maxval) - log10(im->minval);
551         }
552         /* make sure first y=10^x gridline is located on 
553            integer pixel position by moving scale slightly 
554            downwards (sub-pixel movement) */
555         ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
556         ypixfrac = ypix - floor(ypix);
557         if (ypixfrac > 0 && ypixfrac < 1) {
558             double    yfrac = ypixfrac / im->ysize;
560             im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
561             im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
562             ytr(im, DNAN);  /* reset precalc */
563         }
564     } else {
565         /* Make sure we have an integer pixel distance between
566            each minor gridline */
567         double    ypos1 = ytr(im, im->minval);
568         double    ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
569         double    y_pixel_delta = ypos1 - ypos2;
570         double    factor = y_pixel_delta / floor(y_pixel_delta);
571         double    new_range = factor * (im->maxval - im->minval);
572         double    gridstep = im->ygrid_scale.gridstep;
573         double    minor_y, minor_y_px, minor_y_px_frac;
575         if (im->maxval > 0.0)
576             im->maxval = im->minval + new_range;
577         else
578             im->minval = im->maxval - new_range;
579         ytr(im, DNAN);  /* reset precalc */
580         /* make sure first minor gridline is on integer pixel y coord */
581         minor_y = gridstep * floor(im->minval / gridstep);
582         while (minor_y < im->minval)
583             minor_y += gridstep;
584         minor_y_px = ytr(im, minor_y) + im->ysize;  /* ensure > 0 by adding ysize */
585         minor_y_px_frac = minor_y_px - floor(minor_y_px);
586         if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
587             double    yfrac = minor_y_px_frac / im->ysize;
588             double    range = im->maxval - im->minval;
590             im->minval = im->minval - yfrac * range;
591             im->maxval = im->maxval - yfrac * range;
592             ytr(im, DNAN);  /* reset precalc */
593         }
594         calc_horizontal_grid(im);   /* recalc with changed im->maxval */
595     }
598 /* reduce data reimplementation by Alex */
600 void reduce_data(
601     enum cf_en cf,      /* which consolidation function ? */
602     unsigned long cur_step, /* step the data currently is in */
603     time_t *start,      /* start, end and step as requested ... */
604     time_t *end,        /* ... by the application will be   ... */
605     unsigned long *step,    /* ... adjusted to represent reality    */
606     unsigned long *ds_cnt,  /* number of data sources in file */
607     rrd_value_t **data)
608 {                       /* two dimensional array containing the data */
609     int       i, reduce_factor = ceil((double) (*step) / (double) cur_step);
610     unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
611         0;
612     rrd_value_t *srcptr, *dstptr;
614     (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
615     dstptr = *data;
616     srcptr = *data;
617     row_cnt = ((*end) - (*start)) / cur_step;
619 #ifdef DEBUG
620 #define DEBUG_REDUCE
621 #endif
622 #ifdef DEBUG_REDUCE
623     printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
624            row_cnt, reduce_factor, *start, *end, cur_step);
625     for (col = 0; col < row_cnt; col++) {
626         printf("time %10lu: ", *start + (col + 1) * cur_step);
627         for (i = 0; i < *ds_cnt; i++)
628             printf(" %8.2e", srcptr[*ds_cnt * col + i]);
629         printf("\n");
630     }
631 #endif
633     /* We have to combine [reduce_factor] rows of the source
634      ** into one row for the destination.  Doing this we also
635      ** need to take care to combine the correct rows.  First
636      ** alter the start and end time so that they are multiples
637      ** of the new step time.  We cannot reduce the amount of
638      ** time so we have to move the end towards the future and
639      ** the start towards the past.
640      */
641     end_offset = (*end) % (*step);
642     start_offset = (*start) % (*step);
644     /* If there is a start offset (which cannot be more than
645      ** one destination row), skip the appropriate number of
646      ** source rows and one destination row.  The appropriate
647      ** number is what we do know (start_offset/cur_step) of
648      ** the new interval (*step/cur_step aka reduce_factor).
649      */
650 #ifdef DEBUG_REDUCE
651     printf("start_offset: %lu  end_offset: %lu\n", start_offset, end_offset);
652     printf("row_cnt before:  %lu\n", row_cnt);
653 #endif
654     if (start_offset) {
655         (*start) = (*start) - start_offset;
656         skiprows = reduce_factor - start_offset / cur_step;
657         srcptr += skiprows * *ds_cnt;
658         for (col = 0; col < (*ds_cnt); col++)
659             *dstptr++ = DNAN;
660         row_cnt -= skiprows;
661     }
662 #ifdef DEBUG_REDUCE
663     printf("row_cnt between: %lu\n", row_cnt);
664 #endif
666     /* At the end we have some rows that are not going to be
667      ** used, the amount is end_offset/cur_step
668      */
669     if (end_offset) {
670         (*end) = (*end) - end_offset + (*step);
671         skiprows = end_offset / cur_step;
672         row_cnt -= skiprows;
673     }
674 #ifdef DEBUG_REDUCE
675     printf("row_cnt after:   %lu\n", row_cnt);
676 #endif
678 /* Sanity check: row_cnt should be multiple of reduce_factor */
679 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
681     if (row_cnt % reduce_factor) {
682         printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
683                row_cnt, reduce_factor);
684         printf("BUG in reduce_data()\n");
685         exit(1);
686     }
688     /* Now combine reduce_factor intervals at a time
689      ** into one interval for the destination.
690      */
692     for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
693         for (col = 0; col < (*ds_cnt); col++) {
694             rrd_value_t newval = DNAN;
695             unsigned long validval = 0;
697             for (i = 0; i < reduce_factor; i++) {
698                 if (isnan(srcptr[i * (*ds_cnt) + col])) {
699                     continue;
700                 }
701                 validval++;
702                 if (isnan(newval))
703                     newval = srcptr[i * (*ds_cnt) + col];
704                 else {
705                     switch (cf) {
706                     case CF_HWPREDICT:
707                     case CF_DEVSEASONAL:
708                     case CF_DEVPREDICT:
709                     case CF_SEASONAL:
710                     case CF_AVERAGE:
711                         newval += srcptr[i * (*ds_cnt) + col];
712                         break;
713                     case CF_MINIMUM:
714                         newval = min(newval, srcptr[i * (*ds_cnt) + col]);
715                         break;
716                     case CF_FAILURES:
717                         /* an interval contains a failure if any subintervals contained a failure */
718                     case CF_MAXIMUM:
719                         newval = max(newval, srcptr[i * (*ds_cnt) + col]);
720                         break;
721                     case CF_LAST:
722                         newval = srcptr[i * (*ds_cnt) + col];
723                         break;
724                     }
725                 }
726             }
727             if (validval == 0) {
728                 newval = DNAN;
729             } else {
730                 switch (cf) {
731                 case CF_HWPREDICT:
732                 case CF_DEVSEASONAL:
733                 case CF_DEVPREDICT:
734                 case CF_SEASONAL:
735                 case CF_AVERAGE:
736                     newval /= validval;
737                     break;
738                 case CF_MINIMUM:
739                 case CF_FAILURES:
740                 case CF_MAXIMUM:
741                 case CF_LAST:
742                     break;
743                 }
744             }
745             *dstptr++ = newval;
746         }
747         srcptr += (*ds_cnt) * reduce_factor;
748         row_cnt -= reduce_factor;
749     }
750     /* If we had to alter the endtime, we didn't have enough
751      ** source rows to fill the last row. Fill it with NaN.
752      */
753     if (end_offset)
754         for (col = 0; col < (*ds_cnt); col++)
755             *dstptr++ = DNAN;
756 #ifdef DEBUG_REDUCE
757     row_cnt = ((*end) - (*start)) / *step;
758     srcptr = *data;
759     printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
760            row_cnt, *start, *end, *step);
761     for (col = 0; col < row_cnt; col++) {
762         printf("time %10lu: ", *start + (col + 1) * (*step));
763         for (i = 0; i < *ds_cnt; i++)
764             printf(" %8.2e", srcptr[*ds_cnt * col + i]);
765         printf("\n");
766     }
767 #endif
771 /* get the data required for the graphs from the 
772    relevant rrds ... */
774 int data_fetch(
775     image_desc_t *im)
777     int       i, ii;
778     int       skip;
780     /* pull the data from the rrd files ... */
781     for (i = 0; i < (int) im->gdes_c; i++) {
782         /* only GF_DEF elements fetch data */
783         if (im->gdes[i].gf != GF_DEF)
784             continue;
786         skip = 0;
787         /* do we have it already ? */
788         for (ii = 0; ii < i; ii++) {
789             if (im->gdes[ii].gf != GF_DEF)
790                 continue;
791             if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
792                 && (im->gdes[i].cf == im->gdes[ii].cf)
793                 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
794                 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
795                 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
796                 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
797                 /* OK, the data is already there.
798                  ** Just copy the header portion
799                  */
800                 im->gdes[i].start = im->gdes[ii].start;
801                 im->gdes[i].end = im->gdes[ii].end;
802                 im->gdes[i].step = im->gdes[ii].step;
803                 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
804                 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
805                 im->gdes[i].data = im->gdes[ii].data;
806                 im->gdes[i].data_first = 0;
807                 skip = 1;
808             }
809             if (skip)
810                 break;
811         }
812         if (!skip) {
813             unsigned long ft_step = im->gdes[i].step;   /* ft_step will record what we got from fetch */
815             if ((rrd_fetch_fn(im->gdes[i].rrd,
816                               im->gdes[i].cf,
817                               &im->gdes[i].start,
818                               &im->gdes[i].end,
819                               &ft_step,
820                               &im->gdes[i].ds_cnt,
821                               &im->gdes[i].ds_namv,
822                               &im->gdes[i].data)) == -1) {
823                 return -1;
824             }
825             im->gdes[i].data_first = 1;
827             if (ft_step < im->gdes[i].step) {
828                 reduce_data(im->gdes[i].cf_reduce,
829                             ft_step,
830                             &im->gdes[i].start,
831                             &im->gdes[i].end,
832                             &im->gdes[i].step,
833                             &im->gdes[i].ds_cnt, &im->gdes[i].data);
834             } else {
835                 im->gdes[i].step = ft_step;
836             }
837         }
839         /* lets see if the required data source is really there */
840         for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
841             if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
842                 im->gdes[i].ds = ii;
843             }
844         }
845         if (im->gdes[i].ds == -1) {
846             rrd_set_error("No DS called '%s' in '%s'",
847                           im->gdes[i].ds_nam, im->gdes[i].rrd);
848             return -1;
849         }
851     }
852     return 0;
855 /* evaluate the expressions in the CDEF functions */
857 /*************************************************************
858  * CDEF stuff 
859  *************************************************************/
861 long find_var_wrapper(
862     void *arg1,
863     char *key)
865     return find_var((image_desc_t *) arg1, key);
868 /* find gdes containing var*/
869 long find_var(
870     image_desc_t *im,
871     char *key)
873     long      ii;
875     for (ii = 0; ii < im->gdes_c - 1; ii++) {
876         if ((im->gdes[ii].gf == GF_DEF
877              || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
878             && (strcmp(im->gdes[ii].vname, key) == 0)) {
879             return ii;
880         }
881     }
882     return -1;
885 /* find the largest common denominator for all the numbers
886    in the 0 terminated num array */
887 long lcd(
888     long *num)
890     long      rest;
891     int       i;
893     for (i = 0; num[i + 1] != 0; i++) {
894         do {
895             rest = num[i] % num[i + 1];
896             num[i] = num[i + 1];
897             num[i + 1] = rest;
898         } while (rest != 0);
899         num[i + 1] = num[i];
900     }
901 /*    return i==0?num[i]:num[i-1]; */
902     return num[i];
905 /* run the rpn calculator on all the VDEF and CDEF arguments */
906 int data_calc(
907     image_desc_t *im)
910     int       gdi;
911     int       dataidx;
912     long     *steparray, rpi;
913     int       stepcnt;
914     time_t    now;
915     rpnstack_t rpnstack;
917     rpnstack_init(&rpnstack);
919     for (gdi = 0; gdi < im->gdes_c; gdi++) {
920         /* Look for GF_VDEF and GF_CDEF in the same loop,
921          * so CDEFs can use VDEFs and vice versa
922          */
923         switch (im->gdes[gdi].gf) {
924         case GF_XPORT:
925             break;
926         case GF_SHIFT:{
927             graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
929             /* remove current shift */
930             vdp->start -= vdp->shift;
931             vdp->end -= vdp->shift;
933             /* vdef */
934             if (im->gdes[gdi].shidx >= 0)
935                 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
936             /* constant */
937             else
938                 vdp->shift = im->gdes[gdi].shval;
940             /* normalize shift to multiple of consolidated step */
941             vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
943             /* apply shift */
944             vdp->start += vdp->shift;
945             vdp->end += vdp->shift;
946             break;
947         }
948         case GF_VDEF:
949             /* A VDEF has no DS.  This also signals other parts
950              * of rrdtool that this is a VDEF value, not a CDEF.
951              */
952             im->gdes[gdi].ds_cnt = 0;
953             if (vdef_calc(im, gdi)) {
954                 rrd_set_error("Error processing VDEF '%s'",
955                               im->gdes[gdi].vname);
956                 rpnstack_free(&rpnstack);
957                 return -1;
958             }
959             break;
960         case GF_CDEF:
961             im->gdes[gdi].ds_cnt = 1;
962             im->gdes[gdi].ds = 0;
963             im->gdes[gdi].data_first = 1;
964             im->gdes[gdi].start = 0;
965             im->gdes[gdi].end = 0;
966             steparray = NULL;
967             stepcnt = 0;
968             dataidx = -1;
970             /* Find the variables in the expression.
971              * - VDEF variables are substituted by their values
972              *   and the opcode is changed into OP_NUMBER.
973              * - CDEF variables are analized for their step size,
974              *   the lowest common denominator of all the step
975              *   sizes of the data sources involved is calculated
976              *   and the resulting number is the step size for the
977              *   resulting data source.
978              */
979             for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
980                 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
981                     im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
982                     long      ptr = im->gdes[gdi].rpnp[rpi].ptr;
984                     if (im->gdes[ptr].ds_cnt == 0) {    /* this is a VDEF data source */
985 #if 0
986                         printf
987                             ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
988                              im->gdes[gdi].vname, im->gdes[ptr].vname);
989                         printf("DEBUG: value from vdef is %f\n",
990                                im->gdes[ptr].vf.val);
991 #endif
992                         im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
993                         im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
994                     } else {    /* normal variables and PREF(variables) */
996                         /* add one entry to the array that keeps track of the step sizes of the
997                          * data sources going into the CDEF. */
998                         if ((steparray =
999                              rrd_realloc(steparray,
1000                                          (++stepcnt +
1001                                           1) * sizeof(*steparray))) == NULL) {
1002                             rrd_set_error("realloc steparray");
1003                             rpnstack_free(&rpnstack);
1004                             return -1;
1005                         };
1007                         steparray[stepcnt - 1] = im->gdes[ptr].step;
1009                         /* adjust start and end of cdef (gdi) so
1010                          * that it runs from the latest start point
1011                          * to the earliest endpoint of any of the
1012                          * rras involved (ptr)
1013                          */
1015                         if (im->gdes[gdi].start < im->gdes[ptr].start)
1016                             im->gdes[gdi].start = im->gdes[ptr].start;
1018                         if (im->gdes[gdi].end == 0 ||
1019                             im->gdes[gdi].end > im->gdes[ptr].end)
1020                             im->gdes[gdi].end = im->gdes[ptr].end;
1022                         /* store pointer to the first element of
1023                          * the rra providing data for variable,
1024                          * further save step size and data source
1025                          * count of this rra
1026                          */
1027                         im->gdes[gdi].rpnp[rpi].data =
1028                             im->gdes[ptr].data + im->gdes[ptr].ds;
1029                         im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1030                         im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1032                         /* backoff the *.data ptr; this is done so
1033                          * rpncalc() function doesn't have to treat
1034                          * the first case differently
1035                          */
1036                     }   /* if ds_cnt != 0 */
1037                 }       /* if OP_VARIABLE */
1038             }           /* loop through all rpi */
1040             /* move the data pointers to the correct period */
1041             for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1042                 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1043                     im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1044                     long      ptr = im->gdes[gdi].rpnp[rpi].ptr;
1045                     long      diff =
1046                         im->gdes[gdi].start - im->gdes[ptr].start;
1048                     if (diff > 0)
1049                         im->gdes[gdi].rpnp[rpi].data +=
1050                             (diff / im->gdes[ptr].step) *
1051                             im->gdes[ptr].ds_cnt;
1052                 }
1053             }
1055             if (steparray == NULL) {
1056                 rrd_set_error("rpn expressions without DEF"
1057                               " or CDEF variables are not supported");
1058                 rpnstack_free(&rpnstack);
1059                 return -1;
1060             }
1061             steparray[stepcnt] = 0;
1062             /* Now find the resulting step.  All steps in all
1063              * used RRAs have to be visited
1064              */
1065             im->gdes[gdi].step = lcd(steparray);
1066             free(steparray);
1067             if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1068                                                im->gdes[gdi].start)
1069                                               / im->gdes[gdi].step)
1070                                              * sizeof(double))) == NULL) {
1071                 rrd_set_error("malloc im->gdes[gdi].data");
1072                 rpnstack_free(&rpnstack);
1073                 return -1;
1074             }
1076             /* Step through the new cdef results array and
1077              * calculate the values
1078              */
1079             for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1080                  now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1081                 rpnp_t   *rpnp = im->gdes[gdi].rpnp;
1083                 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1084                  * in this case we are advancing by timesteps;
1085                  * we use the fact that time_t is a synonym for long
1086                  */
1087                 if (rpn_calc(rpnp, &rpnstack, (long) now,
1088                              im->gdes[gdi].data, ++dataidx) == -1) {
1089                     /* rpn_calc sets the error string */
1090                     rpnstack_free(&rpnstack);
1091                     return -1;
1092                 }
1093             }           /* enumerate over time steps within a CDEF */
1094             break;
1095         default:
1096             continue;
1097         }
1098     }                   /* enumerate over CDEFs */
1099     rpnstack_free(&rpnstack);
1100     return 0;
1103 /* massage data so, that we get one value for each x coordinate in the graph */
1104 int data_proc(
1105     image_desc_t *im)
1107     long      i, ii;
1108     double    pixstep = (double) (im->end - im->start)
1109         / (double) im->xsize;   /* how much time 
1110                                    passes in one pixel */
1111     double    paintval;
1112     double    minval = DNAN, maxval = DNAN;
1114     unsigned long gr_time;
1116     /* memory for the processed data */
1117     for (i = 0; i < im->gdes_c; i++) {
1118         if ((im->gdes[i].gf == GF_LINE) ||
1119             (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1120             if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1121                                              * sizeof(rrd_value_t))) == NULL) {
1122                 rrd_set_error("malloc data_proc");
1123                 return -1;
1124             }
1125         }
1126     }
1128     for (i = 0; i < im->xsize; i++) {   /* for each pixel */
1129         long      vidx;
1131         gr_time = im->start + pixstep * i;  /* time of the current step */
1132         paintval = 0.0;
1134         for (ii = 0; ii < im->gdes_c; ii++) {
1135             double    value;
1137             switch (im->gdes[ii].gf) {
1138             case GF_LINE:
1139             case GF_AREA:
1140             case GF_TICK:
1141                 if (!im->gdes[ii].stack)
1142                     paintval = 0.0;
1143                 value = im->gdes[ii].yrule;
1144                 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1145                     /* The time of the data doesn't necessarily match
1146                      ** the time of the graph. Beware.
1147                      */
1148                     vidx = im->gdes[ii].vidx;
1149                     if (im->gdes[vidx].gf == GF_VDEF) {
1150                         value = im->gdes[vidx].vf.val;
1151                     } else
1152                         if (((long int) gr_time >=
1153                              (long int) im->gdes[vidx].start)
1154                             && ((long int) gr_time <=
1155                                 (long int) im->gdes[vidx].end)) {
1156                         value = im->gdes[vidx].data[(unsigned long)
1157                                                     floor((double)
1158                                                           (gr_time -
1159                                                            im->gdes[vidx].
1160                                                            start)
1161                                                           /
1162                                                           im->gdes[vidx].step)
1163                                                     * im->gdes[vidx].ds_cnt +
1164                                                     im->gdes[vidx].ds];
1165                     } else {
1166                         value = DNAN;
1167                     }
1168                 };
1170                 if (!isnan(value)) {
1171                     paintval += value;
1172                     im->gdes[ii].p_data[i] = paintval;
1173                     /* GF_TICK: the data values are not
1174                      ** relevant for min and max
1175                      */
1176                     if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1177                         if ((isnan(minval) || paintval < minval) &&
1178                             !(im->logarithmic && paintval <= 0.0))
1179                             minval = paintval;
1180                         if (isnan(maxval) || paintval > maxval)
1181                             maxval = paintval;
1182                     }
1183                 } else {
1184                     im->gdes[ii].p_data[i] = DNAN;
1185                 }
1186                 break;
1187             case GF_STACK:
1188                 rrd_set_error
1189                     ("STACK should already be turned into LINE or AREA here");
1190                 return -1;
1191                 break;
1192             default:
1193                 break;
1194             }
1195         }
1196     }
1198     /* if min or max have not been asigned a value this is because
1199        there was no data in the graph ... this is not good ...
1200        lets set these to dummy values then ... */
1202     if (im->logarithmic) {
1203         if (isnan(minval))
1204             minval = 0.2;
1205         if (isnan(maxval))
1206             maxval = 5.1;
1207     } else {
1208         if (isnan(minval))
1209             minval = 0.0;
1210         if (isnan(maxval))
1211             maxval = 1.0;
1212     }
1214     /* adjust min and max values */
1215     if (isnan(im->minval)
1216         /* don't adjust low-end with log scale *//* why not? */
1217         || ((!im->rigid) && im->minval > minval)
1218         ) {
1219         if (im->logarithmic)
1220             im->minval = minval * 0.5;
1221         else
1222             im->minval = minval;
1223     }
1224     if (isnan(im->maxval)
1225         || (!im->rigid && im->maxval < maxval)
1226         ) {
1227         if (im->logarithmic)
1228             im->maxval = maxval * 2.0;
1229         else
1230             im->maxval = maxval;
1231     }
1232     /* make sure min is smaller than max */
1233     if (im->minval > im->maxval) {
1234         im->minval = 0.99 * im->maxval;
1235     }
1237     /* make sure min and max are not equal */
1238     if (im->minval == im->maxval) {
1239         im->maxval *= 1.01;
1240         if (!im->logarithmic) {
1241             im->minval *= 0.99;
1242         }
1243         /* make sure min and max are not both zero */
1244         if (im->maxval == 0.0) {
1245             im->maxval = 1.0;
1246         }
1247     }
1248     return 0;
1253 /* identify the point where the first gridline, label ... gets placed */
1255 time_t find_first_time(
1256     time_t start,       /* what is the initial time */
1257     enum tmt_en baseint,    /* what is the basic interval */
1258     long basestep       /* how many if these do we jump a time */
1259     )
1261     struct tm tm;
1263     localtime_r(&start, &tm);
1265     switch (baseint) {
1266     case TMT_SECOND:
1267         tm.       tm_sec -= tm.tm_sec % basestep;
1269         break;
1270     case TMT_MINUTE:
1271         tm.       tm_sec = 0;
1272         tm.       tm_min -= tm.tm_min % basestep;
1274         break;
1275     case TMT_HOUR:
1276         tm.       tm_sec = 0;
1277         tm.       tm_min = 0;
1278         tm.       tm_hour -= tm.tm_hour % basestep;
1280         break;
1281     case TMT_DAY:
1282         /* we do NOT look at the basestep for this ... */
1283         tm.       tm_sec = 0;
1284         tm.       tm_min = 0;
1285         tm.       tm_hour = 0;
1287         break;
1288     case TMT_WEEK:
1289         /* we do NOT look at the basestep for this ... */
1290         tm.       tm_sec = 0;
1291         tm.       tm_min = 0;
1292         tm.       tm_hour = 0;
1293         tm.       tm_mday -= tm.tm_wday - 1;    /* -1 because we want the monday */
1295         if (tm.tm_wday == 0)
1296             tm.       tm_mday -= 7; /* we want the *previous* monday */
1298         break;
1299     case TMT_MONTH:
1300         tm.       tm_sec = 0;
1301         tm.       tm_min = 0;
1302         tm.       tm_hour = 0;
1303         tm.       tm_mday = 1;
1304         tm.       tm_mon -= tm.tm_mon % basestep;
1306         break;
1308     case TMT_YEAR:
1309         tm.       tm_sec = 0;
1310         tm.       tm_min = 0;
1311         tm.       tm_hour = 0;
1312         tm.       tm_mday = 1;
1313         tm.       tm_mon = 0;
1314         tm.       tm_year -= (
1315     tm.tm_year + 1900) %basestep;
1317     }
1318     return mktime(&tm);
1321 /* identify the point where the next gridline, label ... gets placed */
1322 time_t find_next_time(
1323     time_t current,     /* what is the initial time */
1324     enum tmt_en baseint,    /* what is the basic interval */
1325     long basestep       /* how many if these do we jump a time */
1326     )
1328     struct tm tm;
1329     time_t    madetime;
1331     localtime_r(&current, &tm);
1333     do {
1334         switch (baseint) {
1335         case TMT_SECOND:
1336             tm.       tm_sec += basestep;
1338             break;
1339         case TMT_MINUTE:
1340             tm.       tm_min += basestep;
1342             break;
1343         case TMT_HOUR:
1344             tm.       tm_hour += basestep;
1346             break;
1347         case TMT_DAY:
1348             tm.       tm_mday += basestep;
1350             break;
1351         case TMT_WEEK:
1352             tm.       tm_mday += 7 * basestep;
1354             break;
1355         case TMT_MONTH:
1356             tm.       tm_mon += basestep;
1358             break;
1359         case TMT_YEAR:
1360             tm.       tm_year += basestep;
1361         }
1362         madetime = mktime(&tm);
1363     } while (madetime == -1);   /* this is necessary to skip impssible times
1364                                    like the daylight saving time skips */
1365     return madetime;
1370 /* calculate values required for PRINT and GPRINT functions */
1372 int print_calc(
1373     image_desc_t *im,
1374     char ***prdata)
1376     long      i, ii, validsteps;
1377     double    printval;
1378     struct tm tmvdef;
1379     int       graphelement = 0;
1380     long      vidx;
1381     int       max_ii;
1382     double    magfact = -1;
1383     char     *si_symb = "";
1384     char     *percent_s;
1385     int       prlines = 1;
1387     /* wow initializing tmvdef is quite a task :-) */
1388     time_t    now = time(NULL);
1390     localtime_r(&now, &tmvdef);
1391     if (im->imginfo)
1392         prlines++;
1393     for (i = 0; i < im->gdes_c; i++) {
1394         vidx = im->gdes[i].vidx;
1395         switch (im->gdes[i].gf) {
1396         case GF_PRINT:
1397             prlines++;
1398             if (((*prdata) =
1399                  rrd_realloc((*prdata), prlines * sizeof(char *))) == NULL) {
1400                 rrd_set_error("realloc prdata");
1401                 return 0;
1402             }
1403         case GF_GPRINT:
1404             /* PRINT and GPRINT can now print VDEF generated values.
1405              * There's no need to do any calculations on them as these
1406              * calculations were already made.
1407              */
1408             if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1409                 printval = im->gdes[vidx].vf.val;
1410                 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1411             } else {    /* need to calculate max,min,avg etcetera */
1412                 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1413                           / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1414                 printval = DNAN;
1415                 validsteps = 0;
1416                 for (ii = im->gdes[vidx].ds;
1417                      ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1418                     if (!finite(im->gdes[vidx].data[ii]))
1419                         continue;
1420                     if (isnan(printval)) {
1421                         printval = im->gdes[vidx].data[ii];
1422                         validsteps++;
1423                         continue;
1424                     }
1426                     switch (im->gdes[i].cf) {
1427                     case CF_HWPREDICT:
1428                     case CF_DEVPREDICT:
1429                     case CF_DEVSEASONAL:
1430                     case CF_SEASONAL:
1431                     case CF_AVERAGE:
1432                         validsteps++;
1433                         printval += im->gdes[vidx].data[ii];
1434                         break;
1435                     case CF_MINIMUM:
1436                         printval = min(printval, im->gdes[vidx].data[ii]);
1437                         break;
1438                     case CF_FAILURES:
1439                     case CF_MAXIMUM:
1440                         printval = max(printval, im->gdes[vidx].data[ii]);
1441                         break;
1442                     case CF_LAST:
1443                         printval = im->gdes[vidx].data[ii];
1444                     }
1445                 }
1446                 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1447                     if (validsteps > 1) {
1448                         printval = (printval / validsteps);
1449                     }
1450                 }
1451             }           /* prepare printval */
1453             if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1454                 /* Magfact is set to -1 upon entry to print_calc.  If it
1455                  * is still less than 0, then we need to run auto_scale.
1456                  * Otherwise, put the value into the correct units.  If
1457                  * the value is 0, then do not set the symbol or magnification
1458                  * so next the calculation will be performed again. */
1459                 if (magfact < 0.0) {
1460                     auto_scale(im, &printval, &si_symb, &magfact);
1461                     if (printval == 0.0)
1462                         magfact = -1.0;
1463                 } else {
1464                     printval /= magfact;
1465                 }
1466                 *(++percent_s) = 's';
1467             } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1468                 auto_scale(im, &printval, &si_symb, &magfact);
1469             }
1471             if (im->gdes[i].gf == GF_PRINT) {
1472                 (*prdata)[prlines - 2] =
1473                     malloc((FMT_LEG_LEN + 2) * sizeof(char));
1474                 (*prdata)[prlines - 1] = NULL;
1475                 if (im->gdes[i].strftm) {
1476                     strftime((*prdata)[prlines - 2], FMT_LEG_LEN,
1477                              im->gdes[i].format, &tmvdef);
1478                 } else {
1479                     if (bad_format(im->gdes[i].format)) {
1480                         rrd_set_error("bad format for PRINT in '%s'",
1481                                       im->gdes[i].format);
1482                         return -1;
1483                     }
1484 #ifdef HAVE_SNPRINTF
1485                     snprintf((*prdata)[prlines - 2], FMT_LEG_LEN,
1486                              im->gdes[i].format, printval, si_symb);
1487 #else
1488                     sprintf((*prdata)[prlines - 2], im->gdes[i].format,
1489                             printval, si_symb);
1490 #endif
1491                 }
1492             } else {
1493                 /* GF_GPRINT */
1495                 if (im->gdes[i].strftm) {
1496                     strftime(im->gdes[i].legend, FMT_LEG_LEN,
1497                              im->gdes[i].format, &tmvdef);
1498                 } else {
1499                     if (bad_format(im->gdes[i].format)) {
1500                         rrd_set_error("bad format for GPRINT in '%s'",
1501                                       im->gdes[i].format);
1502                         return -1;
1503                     }
1504 #ifdef HAVE_SNPRINTF
1505                     snprintf(im->gdes[i].legend, FMT_LEG_LEN - 2,
1506                              im->gdes[i].format, printval, si_symb);
1507 #else
1508                     sprintf(im->gdes[i].legend, im->gdes[i].format, printval,
1509                             si_symb);
1510 #endif
1511                 }
1512                 graphelement = 1;
1513             }
1514             break;
1515         case GF_LINE:
1516         case GF_AREA:
1517         case GF_TICK:
1518             graphelement = 1;
1519             break;
1520         case GF_HRULE:
1521             if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1522                 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1523             };
1524             graphelement = 1;
1525             break;
1526         case GF_VRULE:
1527             if (im->gdes[i].xrule == 0) {   /* again ... the legend printer needs it */
1528                 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1529             };
1530             graphelement = 1;
1531             break;
1532         case GF_COMMENT:
1533         case GF_DEF:
1534         case GF_CDEF:
1535         case GF_VDEF:
1536 #ifdef WITH_PIECHART
1537         case GF_PART:
1538 #endif
1539         case GF_SHIFT:
1540         case GF_XPORT:
1541             break;
1542         case GF_STACK:
1543             rrd_set_error
1544                 ("STACK should already be turned into LINE or AREA here");
1545             return -1;
1546             break;
1547         }
1548     }
1549     return graphelement;
1553 /* place legends with color spots */
1554 int leg_place(
1555     image_desc_t *im,
1556     int *gY)
1558     /* graph labels */
1559     int       interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1560     int       border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1561     int       fill = 0, fill_last;
1562     int       leg_c = 0;
1563     int       leg_x = border, leg_y = im->yimg;
1564     int       leg_y_prev = im->yimg;
1565     int       leg_cc;
1566     int       glue = 0;
1567     int       i, ii, mark = 0;
1568     char      prt_fctn; /*special printfunctions */
1569     int      *legspace;
1571     if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1572         if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1573             rrd_set_error("malloc for legspace");
1574             return -1;
1575         }
1577         if (im->extra_flags & FULL_SIZE_MODE)
1578             leg_y = leg_y_prev =
1579                 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1581         for (i = 0; i < im->gdes_c; i++) {
1582             fill_last = fill;
1584             /* hide legends for rules which are not displayed */
1586             if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1587                 if (im->gdes[i].gf == GF_HRULE &&
1588                     (im->gdes[i].yrule < im->minval
1589                      || im->gdes[i].yrule > im->maxval))
1590                     im->gdes[i].legend[0] = '\0';
1592                 if (im->gdes[i].gf == GF_VRULE &&
1593                     (im->gdes[i].xrule < im->start
1594                      || im->gdes[i].xrule > im->end))
1595                     im->gdes[i].legend[0] = '\0';
1596             }
1598             leg_cc = strlen(im->gdes[i].legend);
1600             /* is there a controle code ant the end of the legend string ? */
1601             /* and it is not a tab \\t */
1602             if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\'
1603                 && im->gdes[i].legend[leg_cc - 1] != 't') {
1604                 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1605                 leg_cc -= 2;
1606                 im->gdes[i].legend[leg_cc] = '\0';
1607             } else {
1608                 prt_fctn = '\0';
1609             }
1610             /* only valid control codes */
1611             if (prt_fctn != 'l' && prt_fctn != 'n' &&   /* a synonym for l */
1612                 prt_fctn != 'r' &&
1613                 prt_fctn != 'j' &&
1614                 prt_fctn != 'c' &&
1615                 prt_fctn != 's' &&
1616                 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1617                 free(legspace);
1618                 rrd_set_error("Unknown control code at the end of '%s\\%c'",
1619                               im->gdes[i].legend, prt_fctn);
1620                 return -1;
1622             }
1624             /* remove exess space */
1625             if (prt_fctn == 'n') {
1626                 prt_fctn = 'l';
1627             }
1629             while (prt_fctn == 'g' &&
1630                    leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1631                 leg_cc--;
1632                 im->gdes[i].legend[leg_cc] = '\0';
1633             }
1634             if (leg_cc != 0) {
1635                 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1637                 if (fill > 0) {
1638                     /* no interleg space if string ends in \g */
1639                     fill += legspace[i];
1640                 }
1641                 fill += gfx_get_text_width(im->canvas, fill + border,
1642                                            im->text_prop[TEXT_PROP_LEGEND].
1643                                            font,
1644                                            im->text_prop[TEXT_PROP_LEGEND].
1645                                            size, im->tabwidth,
1646                                            im->gdes[i].legend, 0);
1647                 leg_c++;
1648             } else {
1649                 legspace[i] = 0;
1650             }
1651             /* who said there was a special tag ... ? */
1652             if (prt_fctn == 'g') {
1653                 prt_fctn = '\0';
1654             }
1655             if (prt_fctn == '\0') {
1656                 if (i == im->gdes_c - 1)
1657                     prt_fctn = 'l';
1659                 /* is it time to place the legends ? */
1660                 if (fill > im->ximg - 2 * border) {
1661                     if (leg_c > 1) {
1662                         /* go back one */
1663                         i--;
1664                         fill = fill_last;
1665                         leg_c--;
1666                         prt_fctn = 'j';
1667                     } else {
1668                         prt_fctn = 'l';
1669                     }
1671                 }
1672             }
1675             if (prt_fctn != '\0') {
1676                 leg_x = border;
1677                 if (leg_c >= 2 && prt_fctn == 'j') {
1678                     glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1679                 } else {
1680                     glue = 0;
1681                 }
1682                 if (prt_fctn == 'c')
1683                     leg_x = (im->ximg - fill) / 2.0;
1684                 if (prt_fctn == 'r')
1685                     leg_x = im->ximg - fill - border;
1687                 for (ii = mark; ii <= i; ii++) {
1688                     if (im->gdes[ii].legend[0] == '\0')
1689                         continue;   /* skip empty legends */
1690                     im->gdes[ii].leg_x = leg_x;
1691                     im->gdes[ii].leg_y = leg_y;
1692                     leg_x +=
1693                         gfx_get_text_width(im->canvas, leg_x,
1694                                            im->text_prop[TEXT_PROP_LEGEND].
1695                                            font,
1696                                            im->text_prop[TEXT_PROP_LEGEND].
1697                                            size, im->tabwidth,
1698                                            im->gdes[ii].legend, 0)
1699                         + legspace[ii]
1700                         + glue;
1701                 }
1702                 leg_y_prev = leg_y;
1703                 if (im->extra_flags & FULL_SIZE_MODE) {
1704                     /* only add y space if there was text on the line */
1705                     if (leg_x > border || prt_fctn == 's')
1706                         leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1707                     if (prt_fctn == 's')
1708                         leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1709                 } else {
1710                     if (leg_x > border || prt_fctn == 's')
1711                         leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1712                     if (prt_fctn == 's')
1713                         leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1714                 }
1715                 fill = 0;
1716                 leg_c = 0;
1717                 mark = ii;
1718             }
1719         }
1721         if (im->extra_flags & FULL_SIZE_MODE) {
1722             if (leg_y != leg_y_prev) {
1723                 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1724                 im->yorigin =
1725                     leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1726             }
1727         } else {
1728             im->yimg = leg_y_prev;
1729             /* if we did place some legends we have to add vertical space */
1730             if (leg_y != im->yimg)
1731                 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1732         }
1733         free(legspace);
1734     }
1735     return 0;
1738 /* create a grid on the graph. it determines what to do
1739    from the values of xsize, start and end */
1741 /* the xaxis labels are determined from the number of seconds per pixel
1742    in the requested graph */
1746 int calc_horizontal_grid(
1747     image_desc_t *im)
1749     double    range;
1750     double    scaledrange;
1751     int       pixel, i;
1752     int       gridind = 0;
1753     int       decimals, fractionals;
1755     im->ygrid_scale.labfact = 2;
1756     range = im->maxval - im->minval;
1757     scaledrange = range / im->magfact;
1759     /* does the scale of this graph make it impossible to put lines
1760        on it? If so, give up. */
1761     if (isnan(scaledrange)) {
1762         return 0;
1763     }
1765     /* find grid spaceing */
1766     pixel = 1;
1767     if (isnan(im->ygridstep)) {
1768         if (im->extra_flags & ALTYGRID) {
1769             /* find the value with max number of digits. Get number of digits */
1770             decimals =
1771                 ceil(log10
1772                      (max(fabs(im->maxval), fabs(im->minval)) *
1773                       im->viewfactor / im->magfact));
1774             if (decimals <= 0)  /* everything is small. make place for zero */
1775                 decimals = 1;
1777             im->ygrid_scale.gridstep =
1778                 pow((double) 10,
1779                     floor(log10(range * im->viewfactor / im->magfact))) /
1780                 im->viewfactor * im->magfact;
1782             if (im->ygrid_scale.gridstep == 0)  /* range is one -> 0.1 is reasonable scale */
1783                 im->ygrid_scale.gridstep = 0.1;
1784             /* should have at least 5 lines but no more then 15 */
1785             if (range / im->ygrid_scale.gridstep < 5)
1786                 im->ygrid_scale.gridstep /= 10;
1787             if (range / im->ygrid_scale.gridstep > 15)
1788                 im->ygrid_scale.gridstep *= 10;
1789             if (range / im->ygrid_scale.gridstep > 5) {
1790                 im->ygrid_scale.labfact = 1;
1791                 if (range / im->ygrid_scale.gridstep > 8)
1792                     im->ygrid_scale.labfact = 2;
1793             } else {
1794                 im->ygrid_scale.gridstep /= 5;
1795                 im->ygrid_scale.labfact = 5;
1796             }
1797             fractionals =
1798                 floor(log10
1799                       (im->ygrid_scale.gridstep *
1800                        (double) im->ygrid_scale.labfact * im->viewfactor /
1801                        im->magfact));
1802             if (fractionals < 0) {  /* small amplitude. */
1803                 int       len = decimals - fractionals + 1;
1805                 if (im->unitslength < len + 2)
1806                     im->unitslength = len + 2;
1807                 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
1808                         -fractionals, (im->symbol != ' ' ? " %c" : ""));
1809             } else {
1810                 int       len = decimals + 1;
1812                 if (im->unitslength < len + 2)
1813                     im->unitslength = len + 2;
1814                 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
1815                         (im->symbol != ' ' ? " %c" : ""));
1816             }
1817         } else {
1818             for (i = 0; ylab[i].grid > 0; i++) {
1819                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1820                 gridind = i;
1821                 if (pixel > 7)
1822                     break;
1823             }
1825             for (i = 0; i < 4; i++) {
1826                 if (pixel * ylab[gridind].lfac[i] >=
1827                     2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1828                     im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1829                     break;
1830                 }
1831             }
1833             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1834         }
1835     } else {
1836         im->ygrid_scale.gridstep = im->ygridstep;
1837         im->ygrid_scale.labfact = im->ylabfact;
1838     }
1839     return 1;
1842 int draw_horizontal_grid(
1843     image_desc_t *im)
1845     int       i;
1846     double    scaledstep;
1847     char      graph_label[100];
1848     int       nlabels = 0;
1849     double    X0 = im->xorigin;
1850     double    X1 = im->xorigin + im->xsize;
1852     int       sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1853     int       egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1854     double    MaxY;
1856     scaledstep =
1857         im->ygrid_scale.gridstep / (double) im->magfact *
1858         (double) im->viewfactor;
1859     MaxY = scaledstep * (double) egrid;
1860     for (i = sgrid; i <= egrid; i++) {
1861         double    Y0 = ytr(im, im->ygrid_scale.gridstep * i);
1862         double    YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
1864         if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
1865             && floor(Y0 + 0.5) <= im->yorigin) {
1866             /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1867                with the chosen settings. Add a label if required by settings, or if
1868                there is only one label so far and the next grid line is out of bounds. */
1869             if (i % im->ygrid_scale.labfact == 0
1870                 || (nlabels == 1
1871                     && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1872                 if (im->symbol == ' ') {
1873                     if (im->extra_flags & ALTYGRID) {
1874                         sprintf(graph_label, im->ygrid_scale.labfmt,
1875                                 scaledstep * (double) i);
1876                     } else {
1877                         if (MaxY < 10) {
1878                             sprintf(graph_label, "%4.1f",
1879                                     scaledstep * (double) i);
1880                         } else {
1881                             sprintf(graph_label, "%4.0f",
1882                                     scaledstep * (double) i);
1883                         }
1884                     }
1885                 } else {
1886                     char      sisym = (i == 0 ? ' ' : im->symbol);
1888                     if (im->extra_flags & ALTYGRID) {
1889                         sprintf(graph_label, im->ygrid_scale.labfmt,
1890                                 scaledstep * (double) i, sisym);
1891                     } else {
1892                         if (MaxY < 10) {
1893                             sprintf(graph_label, "%4.1f %c",
1894                                     scaledstep * (double) i, sisym);
1895                         } else {
1896                             sprintf(graph_label, "%4.0f %c",
1897                                     scaledstep * (double) i, sisym);
1898                         }
1899                     }
1900                 }
1901                 nlabels++;
1903                 gfx_new_text(im->canvas,
1904                              X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
1905                              im->graph_col[GRC_FONT],
1906                              im->text_prop[TEXT_PROP_AXIS].font,
1907                              im->text_prop[TEXT_PROP_AXIS].size,
1908                              im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1909                              graph_label);
1910                 gfx_new_dashed_line(im->canvas,
1911                                     X0 - 2, Y0,
1912                                     X1 + 2, Y0,
1913                                     MGRIDWIDTH, im->graph_col[GRC_MGRID],
1914                                     im->grid_dash_on, im->grid_dash_off);
1916             } else if (!(im->extra_flags & NOMINOR)) {
1917                 gfx_new_dashed_line(im->canvas,
1918                                     X0 - 1, Y0,
1919                                     X1 + 1, Y0,
1920                                     GRIDWIDTH, im->graph_col[GRC_GRID],
1921                                     im->grid_dash_on, im->grid_dash_off);
1923             }
1924         }
1925     }
1926     return 1;
1929 /* this is frexp for base 10 */
1930 double    frexp10(
1931     double,
1932     double *);
1933 double frexp10(
1934     double x,
1935     double *e)
1937     double    mnt;
1938     int       iexp;
1940     iexp = floor(log(fabs(x)) / log(10));
1941     mnt = x / pow(10.0, iexp);
1942     if (mnt >= 10.0) {
1943         iexp++;
1944         mnt = x / pow(10.0, iexp);
1945     }
1946     *e = iexp;
1947     return mnt;
1950 static int AlmostEqual2sComplement(
1951     float A,
1952     float B,
1953     int maxUlps)
1956     int       aInt = *(int *) &A;
1957     int       bInt = *(int *) &B;
1958     int       intDiff;
1960     /* Make sure maxUlps is non-negative and small enough that the
1961        default NAN won't compare as equal to anything.  */
1963     /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1965     /* Make aInt lexicographically ordered as a twos-complement int */
1967     if (aInt < 0)
1968         aInt = 0x80000000l - aInt;
1970     /* Make bInt lexicographically ordered as a twos-complement int */
1972     if (bInt < 0)
1973         bInt = 0x80000000l - bInt;
1975     intDiff = abs(aInt - bInt);
1977     if (intDiff <= maxUlps)
1978         return 1;
1980     return 0;
1983 /* logaritmic horizontal grid */
1984 int horizontal_log_grid(
1985     image_desc_t *im)
1987     double    yloglab[][10] = {
1988         {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1989         {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1990         {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1991         {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1992         {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1993         {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}  /* last line */
1994     };
1996     int       i, j, val_exp, min_exp;
1997     double    nex;      /* number of decades in data */
1998     double    logscale; /* scale in logarithmic space */
1999     int       exfrac = 1;   /* decade spacing */
2000     int       mid = -1; /* row in yloglab for major grid */
2001     double    mspac;    /* smallest major grid spacing (pixels) */
2002     int       flab;     /* first value in yloglab to use */
2003     double    value, tmp, pre_value;
2004     double    X0, X1, Y0;
2005     char      graph_label[100];
2007     nex = log10(im->maxval / im->minval);
2008     logscale = im->ysize / nex;
2010     /* major spacing for data with high dynamic range */
2011     while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2012         if (exfrac == 1)
2013             exfrac = 3;
2014         else
2015             exfrac += 3;
2016     }
2018     /* major spacing for less dynamic data */
2019     do {
2020         /* search best row in yloglab */
2021         mid++;
2022         for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2023         mspac = logscale * log10(10.0 / yloglab[mid][i]);
2024     } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
2025              && yloglab[mid][0] > 0);
2026     if (mid)
2027         mid--;
2029     /* find first value in yloglab */
2030     for (flab = 0;
2031          yloglab[mid][flab] < 10
2032          && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2033     if (yloglab[mid][flab] == 10.0) {
2034         tmp += 1.0;
2035         flab = 0;
2036     }
2037     val_exp = tmp;
2038     if (val_exp % exfrac)
2039         val_exp += abs(-val_exp % exfrac);
2041     X0 = im->xorigin;
2042     X1 = im->xorigin + im->xsize;
2044     /* draw grid */
2045     pre_value = DNAN;
2046     while (1) {
2048         value = yloglab[mid][flab] * pow(10.0, val_exp);
2049         if (AlmostEqual2sComplement(value, pre_value, 4))
2050             break;      /* it seems we are not converging */
2052         pre_value = value;
2054         Y0 = ytr(im, value);
2055         if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2056             break;
2058         /* major grid line */
2059         gfx_new_dashed_line(im->canvas,
2060                             X0 - 2, Y0,
2061                             X1 + 2, Y0,
2062                             MGRIDWIDTH, im->graph_col[GRC_MGRID],
2063                             im->grid_dash_on, im->grid_dash_off);
2065         /* label */
2066         if (im->extra_flags & FORCE_UNITS_SI) {
2067             int       scale;
2068             double    pvalue;
2069             char      symbol;
2071             scale = floor(val_exp / 3.0);
2072             if (value >= 1.0)
2073                 pvalue = pow(10.0, val_exp % 3);
2074             else
2075                 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2076             pvalue *= yloglab[mid][flab];
2078             if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
2079                 ((scale + si_symbcenter) >= 0))
2080                 symbol = si_symbol[scale + si_symbcenter];
2081             else
2082                 symbol = '?';
2084             sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2085         } else
2086             sprintf(graph_label, "%3.0e", value);
2087         gfx_new_text(im->canvas,
2088                      X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
2089                      im->graph_col[GRC_FONT],
2090                      im->text_prop[TEXT_PROP_AXIS].font,
2091                      im->text_prop[TEXT_PROP_AXIS].size,
2092                      im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
2093                      graph_label);
2095         /* minor grid */
2096         if (mid < 4 && exfrac == 1) {
2097             /* find first and last minor line behind current major line
2098              * i is the first line and j tha last */
2099             if (flab == 0) {
2100                 min_exp = val_exp - 1;
2101                 for (i = 1; yloglab[mid][i] < 10.0; i++);
2102                 i = yloglab[mid][i - 1] + 1;
2103                 j = 10;
2104             } else {
2105                 min_exp = val_exp;
2106                 i = yloglab[mid][flab - 1] + 1;
2107                 j = yloglab[mid][flab];
2108             }
2110             /* draw minor lines below current major line */
2111             for (; i < j; i++) {
2113                 value = i * pow(10.0, min_exp);
2114                 if (value < im->minval)
2115                     continue;
2117                 Y0 = ytr(im, value);
2118                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2119                     break;
2121                 /* draw lines */
2122                 gfx_new_dashed_line(im->canvas,
2123                                     X0 - 1, Y0,
2124                                     X1 + 1, Y0,
2125                                     GRIDWIDTH, im->graph_col[GRC_GRID],
2126                                     im->grid_dash_on, im->grid_dash_off);
2127             }
2128         } else if (exfrac > 1) {
2129             for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2130                 value = pow(10.0, i);
2131                 if (value < im->minval)
2132                     continue;
2134                 Y0 = ytr(im, value);
2135                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2136                     break;
2138                 /* draw lines */
2139                 gfx_new_dashed_line(im->canvas,
2140                                     X0 - 1, Y0,
2141                                     X1 + 1, Y0,
2142                                     GRIDWIDTH, im->graph_col[GRC_GRID],
2143                                     im->grid_dash_on, im->grid_dash_off);
2144             }
2145         }
2147         /* next decade */
2148         if (yloglab[mid][++flab] == 10.0) {
2149             flab = 0;
2150             val_exp += exfrac;
2151         }
2152     }
2154     /* draw minor lines after highest major line */
2155     if (mid < 4 && exfrac == 1) {
2156         /* find first and last minor line below current major line
2157          * i is the first line and j tha last */
2158         if (flab == 0) {
2159             min_exp = val_exp - 1;
2160             for (i = 1; yloglab[mid][i] < 10.0; i++);
2161             i = yloglab[mid][i - 1] + 1;
2162             j = 10;
2163         } else {
2164             min_exp = val_exp;
2165             i = yloglab[mid][flab - 1] + 1;
2166             j = yloglab[mid][flab];
2167         }
2169         /* draw minor lines below current major line */
2170         for (; i < j; i++) {
2172             value = i * pow(10.0, min_exp);
2173             if (value < im->minval)
2174                 continue;
2176             Y0 = ytr(im, value);
2177             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2178                 break;
2180             /* draw lines */
2181             gfx_new_dashed_line(im->canvas,
2182                                 X0 - 1, Y0,
2183                                 X1 + 1, Y0,
2184                                 GRIDWIDTH, im->graph_col[GRC_GRID],
2185                                 im->grid_dash_on, im->grid_dash_off);
2186         }
2187     }
2188     /* fancy minor gridlines */
2189     else if (exfrac > 1) {
2190         for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2191             value = pow(10.0, i);
2192             if (value < im->minval)
2193                 continue;
2195             Y0 = ytr(im, value);
2196             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2197                 break;
2199             /* draw lines */
2200             gfx_new_dashed_line(im->canvas,
2201                                 X0 - 1, Y0,
2202                                 X1 + 1, Y0,
2203                                 GRIDWIDTH, im->graph_col[GRC_GRID],
2204                                 im->grid_dash_on, im->grid_dash_off);
2205         }
2206     }
2208     return 1;
2212 void vertical_grid(
2213     image_desc_t *im)
2215     int       xlab_sel; /* which sort of label and grid ? */
2216     time_t    ti, tilab, timajor;
2217     long      factor;
2218     char      graph_label[100];
2219     double    X0, Y0, Y1;   /* points for filled graph and more */
2220     struct tm tm;
2222     /* the type of time grid is determined by finding
2223        the number of seconds per pixel in the graph */
2226     if (im->xlab_user.minsec == -1) {
2227         factor = (im->end - im->start) / im->xsize;
2228         xlab_sel = 0;
2229         while (xlab[xlab_sel + 1].minsec != -1
2230                && xlab[xlab_sel + 1].minsec <= factor) {
2231             xlab_sel++;
2232         }               /* pick the last one */
2233         while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
2234                && xlab[xlab_sel].length > (im->end - im->start)) {
2235             xlab_sel--;
2236         }               /* go back to the smallest size */
2237         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2238         im->xlab_user.gridst = xlab[xlab_sel].gridst;
2239         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2240         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2241         im->xlab_user.labtm = xlab[xlab_sel].labtm;
2242         im->xlab_user.labst = xlab[xlab_sel].labst;
2243         im->xlab_user.precis = xlab[xlab_sel].precis;
2244         im->xlab_user.stst = xlab[xlab_sel].stst;
2245     }
2247     /* y coords are the same for every line ... */
2248     Y0 = im->yorigin;
2249     Y1 = im->yorigin - im->ysize;
2252     /* paint the minor grid */
2253     if (!(im->extra_flags & NOMINOR)) {
2254         for (ti = find_first_time(im->start,
2255                                   im->xlab_user.gridtm,
2256                                   im->xlab_user.gridst),
2257              timajor = find_first_time(im->start,
2258                                        im->xlab_user.mgridtm,
2259                                        im->xlab_user.mgridst);
2260              ti < im->end;
2261              ti =
2262              find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2263             ) {
2264             /* are we inside the graph ? */
2265             if (ti < im->start || ti > im->end)
2266                 continue;
2267             while (timajor < ti) {
2268                 timajor = find_next_time(timajor,
2269                                          im->xlab_user.mgridtm,
2270                                          im->xlab_user.mgridst);
2271             }
2272             if (ti == timajor)
2273                 continue;   /* skip as falls on major grid line */
2274             X0 = xtr(im, ti);
2275             gfx_new_dashed_line(im->canvas, X0, Y0 + 1, X0, Y1 - 1, GRIDWIDTH,
2276                                 im->graph_col[GRC_GRID],
2277                                 im->grid_dash_on, im->grid_dash_off);
2279         }
2280     }
2282     /* paint the major grid */
2283     for (ti = find_first_time(im->start,
2284                               im->xlab_user.mgridtm,
2285                               im->xlab_user.mgridst);
2286          ti < im->end;
2287          ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2288         ) {
2289         /* are we inside the graph ? */
2290         if (ti < im->start || ti > im->end)
2291             continue;
2292         X0 = xtr(im, ti);
2293         gfx_new_dashed_line(im->canvas, X0, Y0 + 3, X0, Y1 - 2, MGRIDWIDTH,
2294                             im->graph_col[GRC_MGRID],
2295                             im->grid_dash_on, im->grid_dash_off);
2297     }
2298     /* paint the labels below the graph */
2299     for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
2300                               im->xlab_user.labtm,
2301                               im->xlab_user.labst);
2302          ti <= im->end - im->xlab_user.precis / 2;
2303          ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2304         ) {
2305         tilab = ti + im->xlab_user.precis / 2;  /* correct time for the label */
2306         /* are we inside the graph ? */
2307         if (tilab < im->start || tilab > im->end)
2308             continue;
2310 #if HAVE_STRFTIME
2311         localtime_r(&tilab, &tm);
2312         strftime(graph_label, 99, im->xlab_user.stst, &tm);
2313 #else
2314 # error "your libc has no strftime I guess we'll abort the exercise here."
2315 #endif
2316         gfx_new_text(im->canvas,
2317                      xtr(im, tilab),
2318                      Y0 + im->text_prop[TEXT_PROP_AXIS].size * 1.4 + 5,
2319                      im->graph_col[GRC_FONT],
2320                      im->text_prop[TEXT_PROP_AXIS].font,
2321                      im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
2322                      GFX_H_CENTER, GFX_V_BOTTOM, graph_label);
2324     }
2329 void axis_paint(
2330     image_desc_t *im)
2332     /* draw x and y axis */
2333     /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2334        im->xorigin+im->xsize,im->yorigin-im->ysize,
2335        GRIDWIDTH, im->graph_col[GRC_AXIS]);
2337        gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2338        im->xorigin+im->xsize,im->yorigin-im->ysize,
2339        GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2341     gfx_new_line(im->canvas, im->xorigin - 4, im->yorigin,
2342                  im->xorigin + im->xsize + 4, im->yorigin,
2343                  MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2345     gfx_new_line(im->canvas, im->xorigin, im->yorigin + 4,
2346                  im->xorigin, im->yorigin - im->ysize - 4,
2347                  MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2350     /* arrow for X and Y axis direction */
2351     gfx_new_area(im->canvas, im->xorigin + im->xsize + 2, im->yorigin - 2, im->xorigin + im->xsize + 2, im->yorigin + 3, im->xorigin + im->xsize + 7, im->yorigin + 0.5,    /* LINEOFFSET */
2352                  im->graph_col[GRC_ARROW]);
2354     gfx_new_area(im->canvas, im->xorigin - 2, im->yorigin - im->ysize - 2, im->xorigin + 3, im->yorigin - im->ysize - 2, im->xorigin + 0.5, im->yorigin - im->ysize - 7,    /* LINEOFFSET */
2355                  im->graph_col[GRC_ARROW]);
2359 void grid_paint(
2360     image_desc_t *im)
2362     long      i;
2363     int       res = 0;
2364     double    X0, Y0;   /* points for filled graph and more */
2365     gfx_node_t *node;
2367     /* draw 3d border */
2368     node = gfx_new_area(im->canvas, 0, im->yimg,
2369                         2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2370     gfx_add_point(node, im->ximg - 2, 2);
2371     gfx_add_point(node, im->ximg, 0);
2372     gfx_add_point(node, 0, 0);
2373 /*    gfx_add_point( node , 0,im->yimg ); */
2375     node = gfx_new_area(im->canvas, 2, im->yimg - 2,
2376                         im->ximg - 2, im->yimg - 2,
2377                         im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2378     gfx_add_point(node, im->ximg, 0);
2379     gfx_add_point(node, im->ximg, im->yimg);
2380     gfx_add_point(node, 0, im->yimg);
2381 /*    gfx_add_point( node , 0,im->yimg ); */
2384     if (im->draw_x_grid == 1)
2385         vertical_grid(im);
2387     if (im->draw_y_grid == 1) {
2388         if (im->logarithmic) {
2389             res = horizontal_log_grid(im);
2390         } else {
2391             res = draw_horizontal_grid(im);
2392         }
2394         /* dont draw horizontal grid if there is no min and max val */
2395         if (!res) {
2396             char     *nodata = "No Data found";
2398             gfx_new_text(im->canvas, im->ximg / 2,
2399                          (2 * im->yorigin - im->ysize) / 2,
2400                          im->graph_col[GRC_FONT],
2401                          im->text_prop[TEXT_PROP_AXIS].font,
2402                          im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2403                          0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2404         }
2405     }
2407     /* yaxis unit description */
2408     gfx_new_text(im->canvas,
2409                  10, (im->yorigin - im->ysize / 2),
2410                  im->graph_col[GRC_FONT],
2411                  im->text_prop[TEXT_PROP_UNIT].font,
2412                  im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2413                  RRDGRAPH_YLEGEND_ANGLE,
2414                  GFX_H_LEFT, GFX_V_CENTER, im->ylegend);
2416     /* graph title */
2417     gfx_new_text(im->canvas,
2418                  im->ximg / 2, im->text_prop[TEXT_PROP_TITLE].size * 1.3 + 4,
2419                  im->graph_col[GRC_FONT],
2420                  im->text_prop[TEXT_PROP_TITLE].font,
2421                  im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2422                  GFX_H_CENTER, GFX_V_CENTER, im->title);
2423     /* rrdtool 'logo' */
2424     gfx_new_text(im->canvas,
2425                  im->ximg - 7, 7,
2426                  (im->graph_col[GRC_FONT] & 0xffffff00) | 0x00000044,
2427                  im->text_prop[TEXT_PROP_AXIS].font,
2428                  5.5, im->tabwidth, 270,
2429                  GFX_H_RIGHT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2431     /* graph watermark */
2432     if (im->watermark[0] != '\0') {
2433         gfx_new_text(im->canvas,
2434                      im->ximg / 2, im->yimg - 6,
2435                      (im->graph_col[GRC_FONT] & 0xffffff00) | 0x00000044,
2436                      im->text_prop[TEXT_PROP_AXIS].font,
2437                      5.5, im->tabwidth, 0,
2438                      GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2439     }
2441     /* graph labels */
2442     if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2443         for (i = 0; i < im->gdes_c; i++) {
2444             if (im->gdes[i].legend[0] == '\0')
2445                 continue;
2447             /* im->gdes[i].leg_y is the bottom of the legend */
2448             X0 = im->gdes[i].leg_x;
2449             Y0 = im->gdes[i].leg_y;
2450             gfx_new_text(im->canvas, X0, Y0,
2451                          im->graph_col[GRC_FONT],
2452                          im->text_prop[TEXT_PROP_LEGEND].font,
2453                          im->text_prop[TEXT_PROP_LEGEND].size,
2454                          im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2455                          im->gdes[i].legend);
2456             /* The legend for GRAPH items starts with "M " to have
2457                enough space for the box */
2458             if (im->gdes[i].gf != GF_PRINT &&
2459                 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2460                 int       boxH, boxV;
2462                 boxH = gfx_get_text_width(im->canvas, 0,
2463                                           im->text_prop[TEXT_PROP_LEGEND].
2464                                           font,
2465                                           im->text_prop[TEXT_PROP_LEGEND].
2466                                           size, im->tabwidth, "o", 0) * 1.2;
2467                 boxV = boxH * 1.1;
2469                 /* make sure transparent colors show up the same way as in the graph */
2470                 node = gfx_new_area(im->canvas,
2471                                     X0, Y0 - boxV,
2472                                     X0, Y0,
2473                                     X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2474                 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2476                 node = gfx_new_area(im->canvas,
2477                                     X0, Y0 - boxV,
2478                                     X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2479                 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2480                 node = gfx_new_line(im->canvas,
2481                                     X0, Y0 - boxV,
2482                                     X0, Y0, 1.0, im->graph_col[GRC_FRAME]);
2483                 gfx_add_point(node, X0 + boxH, Y0);
2484                 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2485                 gfx_close_path(node);
2486             }
2487         }
2488     }
2492 /*****************************************************
2493  * lazy check make sure we rely need to create this graph
2494  *****************************************************/
2496 int lazy_check(
2497     image_desc_t *im)
2499     FILE     *fd = NULL;
2500     int       size = 1;
2501     struct stat imgstat;
2503     if (im->lazy == 0)
2504         return 0;       /* no lazy option */
2505     if (stat(im->graphfile, &imgstat) != 0)
2506         return 0;       /* can't stat */
2507     /* one pixel in the existing graph is more then what we would
2508        change here ... */
2509     if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2510         return 0;
2511     if ((fd = fopen(im->graphfile, "rb")) == NULL)
2512         return 0;       /* the file does not exist */
2513     switch (im->canvas->imgformat) {
2514     case IF_PNG:
2515         size = PngSize(fd, &(im->ximg), &(im->yimg));
2516         break;
2517     default:
2518         size = 1;
2519     }
2520     fclose(fd);
2521     return size;
2524 #ifdef WITH_PIECHART
2525 void pie_part(
2526     image_desc_t *im,
2527     gfx_color_t color,
2528     double PieCenterX,
2529     double PieCenterY,
2530     double Radius,
2531     double startangle,
2532     double endangle)
2534     gfx_node_t *node;
2535     double    angle;
2536     double    step = M_PI / 50; /* Number of iterations for the circle;
2537                                  ** 10 is definitely too low, more than
2538                                  ** 50 seems to be overkill
2539                                  */
2541     /* Strange but true: we have to work clockwise or else
2542      ** anti aliasing nor transparency don't work.
2543      **
2544      ** This test is here to make sure we do it right, also
2545      ** this makes the for...next loop more easy to implement.
2546      ** The return will occur if the user enters a negative number
2547      ** (which shouldn't be done according to the specs) or if the
2548      ** programmers do something wrong (which, as we all know, never
2549      ** happens anyway :)
2550      */
2551     if (endangle < startangle)
2552         return;
2554     /* Hidden feature: Radius decreases each full circle */
2555     angle = startangle;
2556     while (angle >= 2 * M_PI) {
2557         angle -= 2 * M_PI;
2558         Radius *= 0.8;
2559     }
2561     node = gfx_new_area(im->canvas,
2562                         PieCenterX + sin(startangle) * Radius,
2563                         PieCenterY - cos(startangle) * Radius,
2564                         PieCenterX,
2565                         PieCenterY,
2566                         PieCenterX + sin(endangle) * Radius,
2567                         PieCenterY - cos(endangle) * Radius, color);
2568     for (angle = endangle; angle - startangle >= step; angle -= step) {
2569         gfx_add_point(node,
2570                       PieCenterX + sin(angle) * Radius,
2571                       PieCenterY - cos(angle) * Radius);
2572     }
2575 #endif
2577 int graph_size_location(
2578     image_desc_t *im,
2579     int elements
2580 #ifdef WITH_PIECHART
2581     ,
2582     int piechart
2583 #endif
2584     )
2586     /* The actual size of the image to draw is determined from
2587      ** several sources.  The size given on the command line is
2588      ** the graph area but we need more as we have to draw labels
2589      ** and other things outside the graph area
2590      */
2592     int       Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2593 #ifdef WITH_PIECHART
2594         Xpie = 0, Ypie = 0,
2595 #endif
2596         Yxlabel = 0,
2597 #if 0
2598         Xlegend = 0, Ylegend = 0,
2599 #endif
2600         Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2602     if (im->extra_flags & ONLY_GRAPH) {
2603         im->xorigin = 0;
2604         im->ximg = im->xsize;
2605         im->yimg = im->ysize;
2606         im->yorigin = im->ysize;
2607         ytr(im, DNAN);
2608         return 0;
2609     }
2611     /** +---+--------------------------------------------+
2612      ** | y |...............graph title..................|
2613      ** |   +---+-------------------------------+--------+
2614      ** | a | y |                               |        |
2615      ** | x |   |                               |        |
2616      ** | i | a |                               |  pie   |
2617      ** | s | x |       main graph area         | chart  |
2618      ** |   | i |                               |  area  |
2619      ** | t | s |                               |        |
2620      ** | i |   |                               |        |
2621      ** | t | l |                               |        |
2622      ** | l | b +-------------------------------+--------+
2623      ** | e | l |       x axis labels           |        |
2624      ** +---+---+-------------------------------+--------+
2625      ** |....................legends.....................|
2626      ** +------------------------------------------------+
2627      ** |                   watermark                    |
2628      ** +------------------------------------------------+
2629      */
2631     if (im->ylegend[0] != '\0') {
2632         Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2633     }
2635     if (im->title[0] != '\0') {
2636         /* The title is placed "inbetween" two text lines so it
2637          ** automatically has some vertical spacing.  The horizontal
2638          ** spacing is added here, on each side.
2639          */
2640         /* if necessary, reduce the font size of the title until it fits the image width */
2641         Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2642     }
2644     if (elements) {
2645         if (im->draw_x_grid) {
2646             Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2647         }
2648         if (im->draw_y_grid || im->forceleftspace) {
2649             Xylabel = gfx_get_text_width(im->canvas, 0,
2650                                          im->text_prop[TEXT_PROP_AXIS].font,
2651                                          im->text_prop[TEXT_PROP_AXIS].size,
2652                                          im->tabwidth,
2653                                          "0", 0) * im->unitslength;
2654         }
2655     }
2657     if (im->extra_flags & FULL_SIZE_MODE) {
2658         /* The actual size of the image to draw has been determined by the user.
2659          ** The graph area is the space remaining after accounting for the legend,
2660          ** the watermark, the pie chart, the axis labels, and the title.
2661          */
2662         im->xorigin = 0;
2663         im->ximg = im->xsize;
2664         im->yimg = im->ysize;
2665         im->yorigin = im->ysize;
2666         Xmain = im->ximg;
2667         Ymain = im->yimg;
2669         im->yorigin += Ytitle;
2671 #ifdef WITH_PIECHART
2672         if (piechart) {
2673             im->piesize = im->xsize < im->ysize ? im->xsize : im->ysize;
2674             Xpie = im->piesize;
2675             Ypie = im->piesize;
2676         }
2677 #endif
2679         /* Now calculate the total size.  Insert some spacing where
2680            desired.  im->xorigin and im->yorigin need to correspond
2681            with the lower left corner of the main graph area or, if
2682            this one is not set, the imaginary box surrounding the
2683            pie chart area. */
2685         /* Initial size calculation for the main graph area */
2686         Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2687         if (Xmain)
2688             Xmain -= Xspacing;  /* put space between main graph area and right edge */
2690 #ifdef WITH_PIECHART
2691         Xmain -= Xpie;  /* remove pie width from main graph area */
2692         if (Xpie)
2693             Xmain -= Xspacing;  /* put space between pie and main graph area */
2694 #endif
2696         im->xorigin = Xspacing + Xylabel;
2698         /* the length of the title should not influence with width of the graph
2699            if (Xtitle > im->ximg) im->ximg = Xtitle; */
2701         if (Xvertical) {    /* unit description */
2702             Xmain -= Xvertical;
2703             im->xorigin += Xvertical;
2704         }
2705         im->xsize = Xmain;
2706         xtr(im, 0);
2708         /* The vertical size of the image is known in advance.  The main graph area
2709          ** (Ymain) and im->yorigin must be set according to the space requirements
2710          ** of the legend and the axis labels.
2711          */
2713         if (im->extra_flags & NOLEGEND)
2714         {
2715             /* set dimensions correctly if using full size mode with no legend */
2716             im->yorigin = im->yimg - im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2717             Ymain = im->yorigin;
2718         } else
2719         {
2720             /* Determine where to place the legends onto the image.
2721             ** Set Ymain and adjust im->yorigin to match the space requirements.
2722             */
2723             if (leg_place(im, &Ymain) == -1)
2724                return -1;
2725         }
2727 #ifdef WITH_PIECHART
2728         /* if (im->yimg < Ypie) im->yimg = Ypie; * not sure what do about this */
2729 #endif
2731         /* remove title space *or* some padding above the graph from the main graph area */
2732         if (Ytitle) {
2733             Ymain -= Ytitle;
2734         } else {
2735             Ymain -= 1.5 * Yspacing;
2736         }
2738         /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2739         if (im->watermark[0] != '\0') {
2740             Ymain -= Ywatermark;
2741         }
2743         im->ysize = Ymain;
2745     } else {            /* dimension options -width and -height refer to the dimensions of the main graph area */
2747         /* The actual size of the image to draw is determined from
2748          ** several sources.  The size given on the command line is
2749          ** the graph area but we need more as we have to draw labels
2750          ** and other things outside the graph area.
2751          */
2753         if (im->ylegend[0] != '\0') {
2754             Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2755         }
2758         if (im->title[0] != '\0') {
2759             /* The title is placed "inbetween" two text lines so it
2760              ** automatically has some vertical spacing.  The horizontal
2761              ** spacing is added here, on each side.
2762              */
2763             /* don't care for the with of the title
2764                Xtitle = gfx_get_text_width(im->canvas, 0,
2765                im->text_prop[TEXT_PROP_TITLE].font,
2766                im->text_prop[TEXT_PROP_TITLE].size,
2767                im->tabwidth,
2768                im->title, 0) + 2*Xspacing; */
2769             Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2770         }
2772         if (elements) {
2773             Xmain = im->xsize;
2774             Ymain = im->ysize;
2775         }
2776 #ifdef WITH_PIECHART
2777         if (piechart) {
2778             im->piesize = im->xsize < im->ysize ? im->xsize : im->ysize;
2779             Xpie = im->piesize;
2780             Ypie = im->piesize;
2781         }
2782 #endif
2784         /* Now calculate the total size.  Insert some spacing where
2785            desired.  im->xorigin and im->yorigin need to correspond
2786            with the lower left corner of the main graph area or, if
2787            this one is not set, the imaginary box surrounding the
2788            pie chart area. */
2790         /* The legend width cannot yet be determined, as a result we
2791          ** have problems adjusting the image to it.  For now, we just
2792          ** forget about it at all; the legend will have to fit in the
2793          ** size already allocated.
2794          */
2795         im->ximg = Xylabel + Xmain + 2 * Xspacing;
2797 #ifdef WITH_PIECHART
2798         im->ximg += Xpie;
2799 #endif
2801         if (Xmain)
2802             im->ximg += Xspacing;
2803 #ifdef WITH_PIECHART
2804         if (Xpie)
2805             im->ximg += Xspacing;
2806 #endif
2808         im->xorigin = Xspacing + Xylabel;
2810         /* the length of the title should not influence with width of the graph
2811            if (Xtitle > im->ximg) im->ximg = Xtitle; */
2813         if (Xvertical) {    /* unit description */
2814             im->ximg += Xvertical;
2815             im->xorigin += Xvertical;
2816         }
2817         xtr(im, 0);
2819         /* The vertical size is interesting... we need to compare
2820          ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with 
2821          ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2822          ** in order to start even thinking about Ylegend or Ywatermark.
2823          **
2824          ** Do it in three portions: First calculate the inner part,
2825          ** then do the legend, then adjust the total height of the img,
2826          ** adding space for a watermark if one exists;
2827          */
2829         /* reserve space for main and/or pie */
2831         im->yimg = Ymain + Yxlabel;
2833 #ifdef WITH_PIECHART
2834         if (im->yimg < Ypie)
2835             im->yimg = Ypie;
2836 #endif
2838         im->yorigin = im->yimg - Yxlabel;
2840         /* reserve space for the title *or* some padding above the graph */
2841         if (Ytitle) {
2842             im->yimg += Ytitle;
2843             im->yorigin += Ytitle;
2844         } else {
2845             im->yimg += 1.5 * Yspacing;
2846             im->yorigin += 1.5 * Yspacing;
2847         }
2848         /* reserve space for padding below the graph */
2849         im->yimg += Yspacing;
2851         /* Determine where to place the legends onto the image.
2852          ** Adjust im->yimg to match the space requirements.
2853          */
2854         if (leg_place(im, 0) == -1)
2855             return -1;
2857         if (im->watermark[0] != '\0') {
2858             im->yimg += Ywatermark;
2859         }
2860     }
2862 #if 0
2863     if (Xlegend > im->ximg) {
2864         im->ximg = Xlegend;
2865         /* reposition Pie */
2866     }
2867 #endif
2869 #ifdef WITH_PIECHART
2870     /* The pie is placed in the upper right hand corner,
2871      ** just below the title (if any) and with sufficient
2872      ** padding.
2873      */
2874     if (elements) {
2875         im->pie_x = im->ximg - Xspacing - Xpie / 2;
2876         im->pie_y = im->yorigin - Ymain + Ypie / 2;
2877     } else {
2878         im->pie_x = im->ximg / 2;
2879         im->pie_y = im->yorigin - Ypie / 2;
2880     }
2881 #endif
2883     ytr(im, DNAN);
2884     return 0;
2887 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2888 /* yes we are loosing precision by doing tos with floats instead of doubles
2889    but it seems more stable this way. */
2892 /* draw that picture thing ... */
2893 int graph_paint(
2894     image_desc_t *im,
2895     char ***calcpr)
2897     int       i, ii;
2898     int       lazy = lazy_check(im);
2900 #ifdef WITH_PIECHART
2901     int       piechart = 0;
2902     double    PieStart = 0.0;
2903 #endif
2904     FILE     *fo;
2905     gfx_node_t *node;
2907     double    areazero = 0.0;
2908     graph_desc_t *lastgdes = NULL;
2910     /* if we are lazy and there is nothing to PRINT ... quit now */
2911     if (lazy && im->prt_c == 0)
2912         return 0;
2914     /* pull the data from the rrd files ... */
2916     if (data_fetch(im) == -1)
2917         return -1;
2919     /* evaluate VDEF and CDEF operations ... */
2920     if (data_calc(im) == -1)
2921         return -1;
2923 #ifdef WITH_PIECHART
2924     /* check if we need to draw a piechart */
2925     for (i = 0; i < im->gdes_c; i++) {
2926         if (im->gdes[i].gf == GF_PART) {
2927             piechart = 1;
2928             break;
2929         }
2930     }
2931 #endif
2933     /* calculate and PRINT and GPRINT definitions. We have to do it at
2934      * this point because it will affect the length of the legends
2935      * if there are no graph elements we stop here ... 
2936      * if we are lazy, try to quit ... 
2937      */
2938     i = print_calc(im, calcpr);
2939     if (i < 0)
2940         return -1;
2941     if (((i == 0)
2942 #ifdef WITH_PIECHART
2943          && (piechart == 0)
2944 #endif
2945         ) || lazy)
2946         return 0;
2948 #ifdef WITH_PIECHART
2949     /* If there's only the pie chart to draw, signal this */
2950     if (i == 0)
2951         piechart = 2;
2952 #endif
2954 /**************************************************************
2955  *** Calculating sizes and locations became a bit confusing ***
2956  *** so I moved this into a separate function.              ***
2957  **************************************************************/
2958     if (graph_size_location(im, i
2959 #ifdef WITH_PIECHART
2960                             , piechart
2961 #endif
2962         ) == -1)
2963         return -1;
2965     /* get actual drawing data and find min and max values */
2966     if (data_proc(im) == -1)
2967         return -1;
2969     if (!im->logarithmic) {
2970         si_unit(im);
2971     }
2972     /* identify si magnitude Kilo, Mega Giga ? */
2973     if (!im->rigid && !im->logarithmic)
2974         expand_range(im);   /* make sure the upper and lower limit are
2975                                sensible values */
2977     if (!calc_horizontal_grid(im))
2978         return -1;
2980     if (im->gridfit)
2981         apply_gridfit(im);
2983     /* the actual graph is created by going through the individual
2984        graph elements and then drawing them */
2986     node = gfx_new_area(im->canvas,
2987                         0, 0,
2988                         0, im->yimg,
2989                         im->ximg, im->yimg, im->graph_col[GRC_BACK]);
2991     gfx_add_point(node, im->ximg, 0);
2993 #ifdef WITH_PIECHART
2994     if (piechart != 2) {
2995 #endif
2996         node = gfx_new_area(im->canvas,
2997                             im->xorigin, im->yorigin,
2998                             im->xorigin + im->xsize, im->yorigin,
2999                             im->xorigin + im->xsize, im->yorigin - im->ysize,
3000                             im->graph_col[GRC_CANVAS]);
3002         gfx_add_point(node, im->xorigin, im->yorigin - im->ysize);
3004         if (im->minval > 0.0)
3005             areazero = im->minval;
3006         if (im->maxval < 0.0)
3007             areazero = im->maxval;
3008 #ifdef WITH_PIECHART
3009     }
3010 #endif
3012 #ifdef WITH_PIECHART
3013     if (piechart) {
3014         pie_part(im, im->graph_col[GRC_CANVAS], im->pie_x, im->pie_y,
3015                  im->piesize * 0.5, 0, 2 * M_PI);
3016     }
3017 #endif
3019     for (i = 0; i < im->gdes_c; i++) {
3020         switch (im->gdes[i].gf) {
3021         case GF_CDEF:
3022         case GF_VDEF:
3023         case GF_DEF:
3024         case GF_PRINT:
3025         case GF_GPRINT:
3026         case GF_COMMENT:
3027         case GF_HRULE:
3028         case GF_VRULE:
3029         case GF_XPORT:
3030         case GF_SHIFT:
3031             break;
3032         case GF_TICK:
3033             for (ii = 0; ii < im->xsize; ii++) {
3034                 if (!isnan(im->gdes[i].p_data[ii]) &&
3035                     im->gdes[i].p_data[ii] != 0.0) {
3036                     if (im->gdes[i].yrule > 0) {
3037                         gfx_new_line(im->canvas,
3038                                      im->xorigin + ii, im->yorigin,
3039                                      im->xorigin + ii,
3040                                      im->yorigin -
3041                                      im->gdes[i].yrule * im->ysize, 1.0,
3042                                      im->gdes[i].col);
3043                     } else if (im->gdes[i].yrule < 0) {
3044                         gfx_new_line(im->canvas,
3045                                      im->xorigin + ii,
3046                                      im->yorigin - im->ysize,
3047                                      im->xorigin + ii,
3048                                      im->yorigin - (1 -
3049                                                     im->gdes[i].yrule) *
3050                                      im->ysize, 1.0, im->gdes[i].col);
3052                     }
3053                 }
3054             }
3055             break;
3056         case GF_LINE:
3057         case GF_AREA:
3058             /* fix data points at oo and -oo */
3059             for (ii = 0; ii < im->xsize; ii++) {
3060                 if (isinf(im->gdes[i].p_data[ii])) {
3061                     if (im->gdes[i].p_data[ii] > 0) {
3062                         im->gdes[i].p_data[ii] = im->maxval;
3063                     } else {
3064                         im->gdes[i].p_data[ii] = im->minval;
3065                     }
3067                 }
3068             }           /* for */
3070             /* *******************************************************
3071                a           ___. (a,t) 
3072                |   |    ___
3073                ____|   |   |   |
3074                |       |___|
3075                -------|--t-1--t--------------------------------      
3077                if we know the value at time t was a then 
3078                we draw a square from t-1 to t with the value a.
3080                ********************************************************* */
3081             if (im->gdes[i].col != 0x0) {
3082                 /* GF_LINE and friend */
3083                 if (im->gdes[i].gf == GF_LINE) {
3084                     double    last_y = 0.0;
3086                     node = NULL;
3087                     for (ii = 1; ii < im->xsize; ii++) {
3088                         if (isnan(im->gdes[i].p_data[ii])
3089                             || (im->slopemode == 1
3090                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3091                             node = NULL;
3092                             continue;
3093                         }
3094                         if (node == NULL) {
3095                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3096                             if (im->slopemode == 0) {
3097                                 node = gfx_new_line(im->canvas,
3098                                                     ii - 1 + im->xorigin,
3099                                                     last_y, ii + im->xorigin,
3100                                                     last_y,
3101                                                     im->gdes[i].linewidth,
3102                                                     im->gdes[i].col);
3103                             } else {
3104                                 node = gfx_new_line(im->canvas,
3105                                                     ii - 1 + im->xorigin,
3106                                                     ytr(im,
3107                                                         im->gdes[i].
3108                                                         p_data[ii - 1]),
3109                                                     ii + im->xorigin, last_y,
3110                                                     im->gdes[i].linewidth,
3111                                                     im->gdes[i].col);
3112                             }
3113                         } else {
3114                             double    new_y = ytr(im, im->gdes[i].p_data[ii]);
3116                             if (im->slopemode == 0
3117                                 && !AlmostEqual2sComplement(new_y, last_y,
3118                                                             4)) {
3119                                 gfx_add_point(node, ii - 1 + im->xorigin,
3120                                               new_y);
3121                             };
3122                             last_y = new_y;
3123                             gfx_add_point(node, ii + im->xorigin, new_y);
3124                         };
3126                     }
3127                 } else {
3128                     int       idxI = -1;
3129                     double   *foreY = malloc(sizeof(double) * im->xsize * 2);
3130                     double   *foreX = malloc(sizeof(double) * im->xsize * 2);
3131                     double   *backY = malloc(sizeof(double) * im->xsize * 2);
3132                     double   *backX = malloc(sizeof(double) * im->xsize * 2);
3133                     int       drawem = 0;
3135                     for (ii = 0; ii <= im->xsize; ii++) {
3136                         double    ybase, ytop;
3138                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3139                             int       cntI = 1;
3140                             int       lastI = 0;
3142                             while (cntI < idxI
3143                                    && AlmostEqual2sComplement(foreY[lastI],
3144                                                               foreY[cntI], 4)
3145                                    && AlmostEqual2sComplement(foreY[lastI],
3146                                                               foreY[cntI + 1],
3147                                                               4)) {
3148                                 cntI++;
3149                             }
3150                             node = gfx_new_area(im->canvas,
3151                                                 backX[0], backY[0],
3152                                                 foreX[0], foreY[0],
3153                                                 foreX[cntI], foreY[cntI],
3154                                                 im->gdes[i].col);
3155                             while (cntI < idxI) {
3156                                 lastI = cntI;
3157                                 cntI++;
3158                                 while (cntI < idxI
3159                                        &&
3160                                        AlmostEqual2sComplement(foreY[lastI],
3161                                                                foreY[cntI], 4)
3162                                        &&
3163                                        AlmostEqual2sComplement(foreY[lastI],
3164                                                                foreY[cntI +
3165                                                                      1], 4)) {
3166                                     cntI++;
3167                                 }
3168                                 gfx_add_point(node, foreX[cntI], foreY[cntI]);
3169                             }
3170                             gfx_add_point(node, backX[idxI], backY[idxI]);
3171                             while (idxI > 1) {
3172                                 lastI = idxI;
3173                                 idxI--;
3174                                 while (idxI > 1
3175                                        &&
3176                                        AlmostEqual2sComplement(backY[lastI],
3177                                                                backY[idxI], 4)
3178                                        &&
3179                                        AlmostEqual2sComplement(backY[lastI],
3180                                                                backY[idxI -
3181                                                                      1], 4)) {
3182                                     idxI--;
3183                                 }
3184                                 gfx_add_point(node, backX[idxI], backY[idxI]);
3185                             }
3186                             idxI = -1;
3187                             drawem = 0;
3188                         }
3189                         if (drawem != 0) {
3190                             drawem = 0;
3191                             idxI = -1;
3192                         }
3193                         if (ii == im->xsize)
3194                             break;
3196                         /* keep things simple for now, just draw these bars
3197                            do not try to build a big and complex area */
3200                         if (im->slopemode == 0 && ii == 0) {
3201                             continue;
3202                         }
3203                         if (isnan(im->gdes[i].p_data[ii])) {
3204                             drawem = 1;
3205                             continue;
3206                         }
3207                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3208                         if (lastgdes && im->gdes[i].stack) {
3209                             ybase = ytr(im, lastgdes->p_data[ii]);
3210                         } else {
3211                             ybase = ytr(im, areazero);
3212                         }
3213                         if (ybase == ytop) {
3214                             drawem = 1;
3215                             continue;
3216                         }
3217                         /* every area has to be wound clock-wise,
3218                            so we have to make sur base remains base  */
3219                         if (ybase > ytop) {
3220                             double    extra = ytop;
3222                             ytop = ybase;
3223                             ybase = extra;
3224                         }
3225                         if (im->slopemode == 0) {
3226                             backY[++idxI] = ybase - 0.2;
3227                             backX[idxI] = ii + im->xorigin - 1;
3228                             foreY[idxI] = ytop + 0.2;
3229                             foreX[idxI] = ii + im->xorigin - 1;
3230                         }
3231                         backY[++idxI] = ybase - 0.2;
3232                         backX[idxI] = ii + im->xorigin;
3233                         foreY[idxI] = ytop + 0.2;
3234                         foreX[idxI] = ii + im->xorigin;
3235                     }
3236                     /* close up any remaining area */
3237                     free(foreY);
3238                     free(foreX);
3239                     free(backY);
3240                     free(backX);
3241                 }       /* else GF_LINE */
3242             }
3243             /* if color != 0x0 */
3244             /* make sure we do not run into trouble when stacking on NaN */
3245             for (ii = 0; ii < im->xsize; ii++) {
3246                 if (isnan(im->gdes[i].p_data[ii])) {
3247                     if (lastgdes && (im->gdes[i].stack)) {
3248                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3249                     } else {
3250                         im->gdes[i].p_data[ii] = areazero;
3251                     }
3252                 }
3253             }
3254             lastgdes = &(im->gdes[i]);
3255             break;
3256 #ifdef WITH_PIECHART
3257         case GF_PART:
3258             if (isnan(im->gdes[i].yrule))   /* fetch variable */
3259                 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
3261             if (finite(im->gdes[i].yrule)) {    /* even the fetched var can be NaN */
3262                 pie_part(im, im->gdes[i].col,
3263                          im->pie_x, im->pie_y, im->piesize * 0.4,
3264                          M_PI * 2.0 * PieStart / 100.0,
3265                          M_PI * 2.0 * (PieStart + im->gdes[i].yrule) / 100.0);
3266                 PieStart += im->gdes[i].yrule;
3267             }
3268             break;
3269 #endif
3270         case GF_STACK:
3271             rrd_set_error
3272                 ("STACK should already be turned into LINE or AREA here");
3273             return -1;
3274             break;
3276         }               /* switch */
3277     }
3278 #ifdef WITH_PIECHART
3279     if (piechart == 2) {
3280         im->draw_x_grid = 0;
3281         im->draw_y_grid = 0;
3282     }
3283 #endif
3286     /* grid_paint also does the text */
3287     if (!(im->extra_flags & ONLY_GRAPH))
3288         grid_paint(im);
3291     if (!(im->extra_flags & ONLY_GRAPH))
3292         axis_paint(im);
3294     /* the RULES are the last thing to paint ... */
3295     for (i = 0; i < im->gdes_c; i++) {
3297         switch (im->gdes[i].gf) {
3298         case GF_HRULE:
3299             if (im->gdes[i].yrule >= im->minval
3300                 && im->gdes[i].yrule <= im->maxval)
3301                 gfx_new_line(im->canvas,
3302                              im->xorigin, ytr(im, im->gdes[i].yrule),
3303                              im->xorigin + im->xsize, ytr(im,
3304                                                           im->gdes[i].yrule),
3305                              1.0, im->gdes[i].col);
3306             break;
3307         case GF_VRULE:
3308             if (im->gdes[i].xrule >= im->start
3309                 && im->gdes[i].xrule <= im->end)
3310                 gfx_new_line(im->canvas,
3311                              xtr(im, im->gdes[i].xrule), im->yorigin,
3312                              xtr(im, im->gdes[i].xrule),
3313                              im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3314             break;
3315         default:
3316             break;
3317         }
3318     }
3321     if (strcmp(im->graphfile, "-") == 0) {
3322         fo = im->graphhandle ? im->graphhandle : stdout;
3323 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3324         /* Change translation mode for stdout to BINARY */
3325         _setmode(_fileno(fo), O_BINARY);
3326 #endif
3327     } else {
3328         if ((fo = fopen(im->graphfile, "wb")) == NULL) {
3329             rrd_set_error("Opening '%s' for write: %s", im->graphfile,
3330                           rrd_strerror(errno));
3331             return (-1);
3332         }
3333     }
3334     gfx_render(im->canvas, im->ximg, im->yimg, 0x00000000, fo);
3335     if (strcmp(im->graphfile, "-") != 0)
3336         fclose(fo);
3337     return 0;
3341 /*****************************************************
3342  * graph stuff 
3343  *****************************************************/
3345 int gdes_alloc(
3346     image_desc_t *im)
3349     im->gdes_c++;
3350     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3351                                                  * sizeof(graph_desc_t))) ==
3352         NULL) {
3353         rrd_set_error("realloc graph_descs");
3354         return -1;
3355     }
3358     im->gdes[im->gdes_c - 1].step = im->step;
3359     im->gdes[im->gdes_c - 1].step_orig = im->step;
3360     im->gdes[im->gdes_c - 1].stack = 0;
3361     im->gdes[im->gdes_c - 1].linewidth = 0;
3362     im->gdes[im->gdes_c - 1].debug = 0;
3363     im->gdes[im->gdes_c - 1].start = im->start;
3364     im->gdes[im->gdes_c - 1].start_orig = im->start;
3365     im->gdes[im->gdes_c - 1].end = im->end;
3366     im->gdes[im->gdes_c - 1].end_orig = im->end;
3367     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3368     im->gdes[im->gdes_c - 1].data = NULL;
3369     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3370     im->gdes[im->gdes_c - 1].data_first = 0;
3371     im->gdes[im->gdes_c - 1].p_data = NULL;
3372     im->gdes[im->gdes_c - 1].rpnp = NULL;
3373     im->gdes[im->gdes_c - 1].shift = 0;
3374     im->gdes[im->gdes_c - 1].col = 0x0;
3375     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3376     im->gdes[im->gdes_c - 1].format[0] = '\0';
3377     im->gdes[im->gdes_c - 1].strftm = 0;
3378     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3379     im->gdes[im->gdes_c - 1].ds = -1;
3380     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3381     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3382     im->gdes[im->gdes_c - 1].p_data = NULL;
3383     im->gdes[im->gdes_c - 1].yrule = DNAN;
3384     im->gdes[im->gdes_c - 1].xrule = 0;
3385     return 0;
3388 /* copies input untill the first unescaped colon is found
3389    or until input ends. backslashes have to be escaped as well */
3390 int scan_for_col(
3391     const char *const input,
3392     int len,
3393     char *const output)
3395     int       inp, outp = 0;
3397     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3398         if (input[inp] == '\\' &&
3399             input[inp + 1] != '\0' &&
3400             (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3401             output[outp++] = input[++inp];
3402         } else {
3403             output[outp++] = input[inp];
3404         }
3405     }
3406     output[outp] = '\0';
3407     return inp;
3410 /* Some surgery done on this function, it became ridiculously big.
3411 ** Things moved:
3412 ** - initializing     now in rrd_graph_init()
3413 ** - options parsing  now in rrd_graph_options()
3414 ** - script parsing   now in rrd_graph_script()
3415 */
3416 int rrd_graph(
3417     int argc,
3418     char **argv,
3419     char ***prdata,
3420     int *xsize,
3421     int *ysize,
3422     FILE * stream,
3423     double *ymin,
3424     double *ymax)
3426     image_desc_t im;
3428     rrd_graph_init(&im);
3429     im.graphhandle = stream;
3431     rrd_graph_options(argc, argv, &im);
3432     if (rrd_test_error()) {
3433         im_free(&im);
3434         return -1;
3435     }
3437     if (strlen(argv[optind]) >= MAXPATH) {
3438         rrd_set_error("filename (including path) too long");
3439         im_free(&im);
3440         return -1;
3441     }
3442     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3443     im.graphfile[MAXPATH - 1] = '\0';
3445     rrd_graph_script(argc, argv, &im, 1);
3446     if (rrd_test_error()) {
3447         im_free(&im);
3448         return -1;
3449     }
3451     /* Everything is now read and the actual work can start */
3453     (*prdata) = NULL;
3454     if (graph_paint(&im, prdata) == -1) {
3455         im_free(&im);
3456         return -1;
3457     }
3459     /* The image is generated and needs to be output.
3460      ** Also, if needed, print a line with information about the image.
3461      */
3463     *xsize = im.ximg;
3464     *ysize = im.yimg;
3465     *ymin = im.minval;
3466     *ymax = im.maxval;
3467     if (im.imginfo) {
3468         char     *filename;
3470         if (!(*prdata)) {
3471             /* maybe prdata is not allocated yet ... lets do it now */
3472             if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3473                 rrd_set_error("malloc imginfo");
3474                 return -1;
3475             };
3476         }
3477         if (((*prdata)[0] =
3478              malloc((strlen(im.imginfo) + 200 +
3479                      strlen(im.graphfile)) * sizeof(char)))
3480             == NULL) {
3481             rrd_set_error("malloc imginfo");
3482             return -1;
3483         }
3484         filename = im.graphfile + strlen(im.graphfile);
3485         while (filename > im.graphfile) {
3486             if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3487                 break;
3488             filename--;
3489         }
3491         sprintf((*prdata)[0], im.imginfo, filename,
3492                 (long) (im.canvas->zoom * im.ximg),
3493                 (long) (im.canvas->zoom * im.yimg));
3494     }
3495     im_free(&im);
3496     return 0;
3499 void rrd_graph_init(
3500     image_desc_t *im)
3502     unsigned int i;
3504 #ifdef HAVE_TZSET
3505     tzset();
3506 #endif
3507 #ifdef HAVE_SETLOCALE
3508     setlocale(LC_TIME, "");
3509 #ifdef HAVE_MBSTOWCS
3510     setlocale(LC_CTYPE, "");
3511 #endif
3512 #endif
3513     im->yorigin = 0;
3514     im->xorigin = 0;
3515     im->minval = 0;
3516     im->xlab_user.minsec = -1;
3517     im->ximg = 0;
3518     im->yimg = 0;
3519     im->xsize = 400;
3520     im->ysize = 100;
3521     im->step = 0;
3522     im->ylegend[0] = '\0';
3523     im->title[0] = '\0';
3524     im->watermark[0] = '\0';
3525     im->minval = DNAN;
3526     im->maxval = DNAN;
3527     im->unitsexponent = 9999;
3528     im->unitslength = 6;
3529     im->forceleftspace = 0;
3530     im->symbol = ' ';
3531     im->viewfactor = 1.0;
3532     im->extra_flags = 0;
3533     im->rigid = 0;
3534     im->gridfit = 1;
3535     im->imginfo = NULL;
3536     im->lazy = 0;
3537     im->slopemode = 0;
3538     im->logarithmic = 0;
3539     im->ygridstep = DNAN;
3540     im->draw_x_grid = 1;
3541     im->draw_y_grid = 1;
3542     im->base = 1000;
3543     im->prt_c = 0;
3544     im->gdes_c = 0;
3545     im->gdes = NULL;
3546     im->canvas = gfx_new_canvas();
3547     im->grid_dash_on = 1;
3548     im->grid_dash_off = 1;
3549     im->tabwidth = 40.0;
3551     for (i = 0; i < DIM(graph_col); i++)
3552         im->graph_col[i] = graph_col[i];
3554 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3555     {
3556         char     *windir;
3557         char      rrd_win_default_font[1000];
3559         windir = getenv("windir");
3560         /* %windir% is something like D:\windows or C:\winnt */
3561         if (windir != NULL) {
3562             strncpy(rrd_win_default_font, windir, 500);
3563             rrd_win_default_font[500] = '\0';
3564             strcat(rrd_win_default_font, "\\fonts\\");
3565             strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3566             for (i = 0; i < DIM(text_prop); i++) {
3567                 strncpy(text_prop[i].font, rrd_win_default_font,
3568                         sizeof(text_prop[i].font) - 1);
3569                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3570             }
3571         }
3572     }
3573 #endif
3574     {
3575         char     *deffont;
3577         deffont = getenv("RRD_DEFAULT_FONT");
3578         if (deffont != NULL) {
3579             for (i = 0; i < DIM(text_prop); i++) {
3580                 strncpy(text_prop[i].font, deffont,
3581                         sizeof(text_prop[i].font) - 1);
3582                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3583             }
3584         }
3585     }
3586     for (i = 0; i < DIM(text_prop); i++) {
3587         im->text_prop[i].size = text_prop[i].size;
3588         strcpy(im->text_prop[i].font, text_prop[i].font);
3589     }
3592 void rrd_graph_options(
3593     int argc,
3594     char *argv[],
3595     image_desc_t *im)
3597     int       stroff;
3598     char     *parsetime_error = NULL;
3599     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3600     time_t    start_tmp = 0, end_tmp = 0;
3601     long      long_tmp;
3602     struct rrd_time_value start_tv, end_tv;
3603     gfx_color_t color;
3605     optind = 0;
3606     opterr = 0;         /* initialize getopt */
3608     parsetime("end-24h", &start_tv);
3609     parsetime("now", &end_tv);
3611     /* defines for long options without a short equivalent. should be bytes,
3612        and may not collide with (the ASCII value of) short options */
3613 #define LONGOPT_UNITS_SI 255
3615     while (1) {
3616         static struct option long_options[] = {
3617             {"start", required_argument, 0, 's'},
3618             {"end", required_argument, 0, 'e'},
3619             {"x-grid", required_argument, 0, 'x'},
3620             {"y-grid", required_argument, 0, 'y'},
3621             {"vertical-label", required_argument, 0, 'v'},
3622             {"width", required_argument, 0, 'w'},
3623             {"height", required_argument, 0, 'h'},
3624             {"full-size-mode", no_argument, 0, 'D'},
3625             {"interlaced", no_argument, 0, 'i'},
3626             {"upper-limit", required_argument, 0, 'u'},
3627             {"lower-limit", required_argument, 0, 'l'},
3628             {"rigid", no_argument, 0, 'r'},
3629             {"base", required_argument, 0, 'b'},
3630             {"logarithmic", no_argument, 0, 'o'},
3631             {"color", required_argument, 0, 'c'},
3632             {"font", required_argument, 0, 'n'},
3633             {"title", required_argument, 0, 't'},
3634             {"imginfo", required_argument, 0, 'f'},
3635             {"imgformat", required_argument, 0, 'a'},
3636             {"lazy", no_argument, 0, 'z'},
3637             {"zoom", required_argument, 0, 'm'},
3638             {"no-legend", no_argument, 0, 'g'},
3639             {"force-rules-legend", no_argument, 0, 'F'},
3640             {"only-graph", no_argument, 0, 'j'},
3641             {"alt-y-grid", no_argument, 0, 'Y'},
3642             {"no-minor", no_argument, 0, 'I'},
3643             {"slope-mode", no_argument, 0, 'E'},
3644             {"alt-autoscale", no_argument, 0, 'A'},
3645             {"alt-autoscale-min", no_argument, 0, 'J'},
3646             {"alt-autoscale-max", no_argument, 0, 'M'},
3647             {"no-gridfit", no_argument, 0, 'N'},
3648             {"units-exponent", required_argument, 0, 'X'},
3649             {"units-length", required_argument, 0, 'L'},
3650             {"units", required_argument, 0, LONGOPT_UNITS_SI},
3651             {"step", required_argument, 0, 'S'},
3652             {"tabwidth", required_argument, 0, 'T'},
3653             {"font-render-mode", required_argument, 0, 'R'},
3654             {"font-smoothing-threshold", required_argument, 0, 'B'},
3655             {"watermark", required_argument, 0, 'W'},
3656             {"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 */
3657             {0, 0, 0, 0}
3658         };
3659         int       option_index = 0;
3660         int       opt;
3661         int       col_start, col_end;
3663         opt = getopt_long(argc, argv,
3664                           "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:",
3665                           long_options, &option_index);
3667         if (opt == EOF)
3668             break;
3670         switch (opt) {
3671         case 'I':
3672             im->extra_flags |= NOMINOR;
3673             break;
3674         case 'Y':
3675             im->extra_flags |= ALTYGRID;
3676             break;
3677         case 'A':
3678             im->extra_flags |= ALTAUTOSCALE;
3679             break;
3680         case 'J':
3681             im->extra_flags |= ALTAUTOSCALE_MIN;
3682             break;
3683         case 'M':
3684             im->extra_flags |= ALTAUTOSCALE_MAX;
3685             break;
3686         case 'j':
3687             im->extra_flags |= ONLY_GRAPH;
3688             break;
3689         case 'g':
3690             im->extra_flags |= NOLEGEND;
3691             break;
3692         case 'F':
3693             im->extra_flags |= FORCE_RULES_LEGEND;
3694             break;
3695         case LONGOPT_UNITS_SI:
3696             if (im->extra_flags & FORCE_UNITS) {
3697                 rrd_set_error("--units can only be used once!");
3698                 return;
3699             }
3700             if (strcmp(optarg, "si") == 0)
3701                 im->extra_flags |= FORCE_UNITS_SI;
3702             else {
3703                 rrd_set_error("invalid argument for --units: %s", optarg);
3704                 return;
3705             }
3706             break;
3707         case 'X':
3708             im->unitsexponent = atoi(optarg);
3709             break;
3710         case 'L':
3711             im->unitslength = atoi(optarg);
3712             im->forceleftspace = 1;
3713             break;
3714         case 'T':
3715             im->tabwidth = atof(optarg);
3716             break;
3717         case 'S':
3718             im->step = atoi(optarg);
3719             break;
3720         case 'N':
3721             im->gridfit = 0;
3722             break;
3723         case 's':
3724             if ((parsetime_error = parsetime(optarg, &start_tv))) {
3725                 rrd_set_error("start time: %s", parsetime_error);
3726                 return;
3727             }
3728             break;
3729         case 'e':
3730             if ((parsetime_error = parsetime(optarg, &end_tv))) {
3731                 rrd_set_error("end time: %s", parsetime_error);
3732                 return;
3733             }
3734             break;
3735         case 'x':
3736             if (strcmp(optarg, "none") == 0) {
3737                 im->draw_x_grid = 0;
3738                 break;
3739             };
3741             if (sscanf(optarg,
3742                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3743                        scan_gtm,
3744                        &im->xlab_user.gridst,
3745                        scan_mtm,
3746                        &im->xlab_user.mgridst,
3747                        scan_ltm,
3748                        &im->xlab_user.labst,
3749                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3750                 strncpy(im->xlab_form, optarg + stroff,
3751                         sizeof(im->xlab_form) - 1);
3752                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3753                 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3754                     rrd_set_error("unknown keyword %s", scan_gtm);
3755                     return;
3756                 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3757                            == -1) {
3758                     rrd_set_error("unknown keyword %s", scan_mtm);
3759                     return;
3760                 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3761                            -1) {
3762                     rrd_set_error("unknown keyword %s", scan_ltm);
3763                     return;
3764                 }
3765                 im->xlab_user.minsec = 1;
3766                 im->xlab_user.stst = im->xlab_form;
3767             } else {
3768                 rrd_set_error("invalid x-grid format");
3769                 return;
3770             }
3771             break;
3772         case 'y':
3774             if (strcmp(optarg, "none") == 0) {
3775                 im->draw_y_grid = 0;
3776                 break;
3777             };
3779             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3780                 if (im->ygridstep <= 0) {
3781                     rrd_set_error("grid step must be > 0");
3782                     return;
3783                 } else if (im->ylabfact < 1) {
3784                     rrd_set_error("label factor must be > 0");
3785                     return;
3786                 }
3787             } else {
3788                 rrd_set_error("invalid y-grid format");
3789                 return;
3790             }
3791             break;
3792         case 'v':
3793             strncpy(im->ylegend, optarg, 150);
3794             im->ylegend[150] = '\0';
3795             break;
3796         case 'u':
3797             im->maxval = atof(optarg);
3798             break;
3799         case 'l':
3800             im->minval = atof(optarg);
3801             break;
3802         case 'b':
3803             im->base = atol(optarg);
3804             if (im->base != 1024 && im->base != 1000) {
3805                 rrd_set_error
3806                     ("the only sensible value for base apart from 1000 is 1024");
3807                 return;
3808             }
3809             break;
3810         case 'w':
3811             long_tmp = atol(optarg);
3812             if (long_tmp < 10) {
3813                 rrd_set_error("width below 10 pixels");
3814                 return;
3815             }
3816             im->xsize = long_tmp;
3817             break;
3818         case 'h':
3819             long_tmp = atol(optarg);
3820             if (long_tmp < 10) {
3821                 rrd_set_error("height below 10 pixels");
3822                 return;
3823             }
3824             im->ysize = long_tmp;
3825             break;
3826         case 'D':
3827             im->extra_flags |= FULL_SIZE_MODE;
3828             break;
3829         case 'i':
3830             im->canvas->interlaced = 1;
3831             break;
3832         case 'r':
3833             im->rigid = 1;
3834             break;
3835         case 'f':
3836             im->imginfo = optarg;
3837             break;
3838         case 'a':
3839             if ((int) (im->canvas->imgformat = if_conv(optarg)) == -1) {
3840                 rrd_set_error("unsupported graphics format '%s'", optarg);
3841                 return;
3842             }
3843             break;
3844         case 'z':
3845             im->lazy = 1;
3846             break;
3847         case 'E':
3848             im->slopemode = 1;
3849             break;
3851         case 'o':
3852             im->logarithmic = 1;
3853             break;
3854         case 'c':
3855             if (sscanf(optarg,
3856                        "%10[A-Z]#%n%8lx%n",
3857                        col_nam, &col_start, &color, &col_end) == 2) {
3858                 int       ci;
3859                 int       col_len = col_end - col_start;
3861                 switch (col_len) {
3862                 case 3:
3863                     color = (((color & 0xF00) * 0x110000) |
3864                              ((color & 0x0F0) * 0x011000) |
3865                              ((color & 0x00F) * 0x001100) | 0x000000FF);
3866                     break;
3867                 case 4:
3868                     color = (((color & 0xF000) * 0x11000) |
3869                              ((color & 0x0F00) * 0x01100) |
3870                              ((color & 0x00F0) * 0x00110) |
3871                              ((color & 0x000F) * 0x00011)
3872                         );
3873                     break;
3874                 case 6:
3875                     color = (color << 8) + 0xff /* shift left by 8 */ ;
3876                     break;
3877                 case 8:
3878                     break;
3879                 default:
3880                     rrd_set_error("the color format is #RRGGBB[AA]");
3881                     return;
3882                 }
3883                 if ((ci = grc_conv(col_nam)) != -1) {
3884                     im->graph_col[ci] = color;
3885                 } else {
3886                     rrd_set_error("invalid color name '%s'", col_nam);
3887                     return;
3888                 }
3889             } else {
3890                 rrd_set_error("invalid color def format");
3891                 return;
3892             }
3893             break;
3894         case 'n':{
3895             char      prop[15];
3896             double    size = 1;
3897             char      font[1024] = "";
3899             if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
3900                 int       sindex, propidx;
3902                 if ((sindex = text_prop_conv(prop)) != -1) {
3903                     for (propidx = sindex; propidx < TEXT_PROP_LAST;
3904                          propidx++) {
3905                         if (size > 0) {
3906                             im->text_prop[propidx].size = size;
3907                         }
3908                         if (strlen(font) > 0) {
3909                             strcpy(im->text_prop[propidx].font, font);
3910                         }
3911                         if (propidx == sindex && sindex != 0)
3912                             break;
3913                     }
3914                 } else {
3915                     rrd_set_error("invalid fonttag '%s'", prop);
3916                     return;
3917                 }
3918             } else {
3919                 rrd_set_error("invalid text property format");
3920                 return;
3921             }
3922             break;
3923         }
3924         case 'm':
3925             im->canvas->zoom = atof(optarg);
3926             if (im->canvas->zoom <= 0.0) {
3927                 rrd_set_error("zoom factor must be > 0");
3928                 return;
3929             }
3930             break;
3931         case 't':
3932             strncpy(im->title, optarg, 150);
3933             im->title[150] = '\0';
3934             break;
3936         case 'R':
3937             if (strcmp(optarg, "normal") == 0)
3938                 im->canvas->aa_type = AA_NORMAL;
3939             else if (strcmp(optarg, "light") == 0)
3940                 im->canvas->aa_type = AA_LIGHT;
3941             else if (strcmp(optarg, "mono") == 0)
3942                 im->canvas->aa_type = AA_NONE;
3943             else {
3944                 rrd_set_error("unknown font-render-mode '%s'", optarg);
3945                 return;
3946             }
3947             break;
3949         case 'B':
3950             im->canvas->font_aa_threshold = atof(optarg);
3951             break;
3953         case 'W':
3954             strncpy(im->watermark, optarg, 100);
3955             im->watermark[99] = '\0';
3956             break;
3958         case '?':
3959             if (optopt != 0)
3960                 rrd_set_error("unknown option '%c'", optopt);
3961             else
3962                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
3963             return;
3964         }
3965     }
3967     if (optind >= argc) {
3968         rrd_set_error("missing filename");
3969         return;
3970     }
3972     if (im->logarithmic == 1 && im->minval <= 0) {
3973         rrd_set_error
3974             ("for a logarithmic yaxis you must specify a lower-limit > 0");
3975         return;
3976     }
3978     if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
3979         /* error string is set in parsetime.c */
3980         return;
3981     }
3983     if (start_tmp < 3600 * 24 * 365 * 10) {
3984         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
3985                       start_tmp);
3986         return;
3987     }
3989     if (end_tmp < start_tmp) {
3990         rrd_set_error("start (%ld) should be less than end (%ld)",
3991                       start_tmp, end_tmp);
3992         return;
3993     }
3995     im->start = start_tmp;
3996     im->end = end_tmp;
3997     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4000 int rrd_graph_color(
4001     image_desc_t *im,
4002     char *var,
4003     char *err,
4004     int optional)
4006     char     *color;
4007     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4009     color = strstr(var, "#");
4010     if (color == NULL) {
4011         if (optional == 0) {
4012             rrd_set_error("Found no color in %s", err);
4013             return 0;
4014         }
4015         return 0;
4016     } else {
4017         int       n = 0;
4018         char     *rest;
4019         gfx_color_t col;
4021         rest = strstr(color, ":");
4022         if (rest != NULL)
4023             n = rest - color;
4024         else
4025             n = strlen(color);
4027         switch (n) {
4028         case 7:
4029             sscanf(color, "#%6lx%n", &col, &n);
4030             col = (col << 8) + 0xff /* shift left by 8 */ ;
4031             if (n != 7)
4032                 rrd_set_error("Color problem in %s", err);
4033             break;
4034         case 9:
4035             sscanf(color, "#%8lx%n", &col, &n);
4036             if (n == 9)
4037                 break;
4038         default:
4039             rrd_set_error("Color problem in %s", err);
4040         }
4041         if (rrd_test_error())
4042             return 0;
4043         gdp->col = col;
4044         return n;
4045     }
4049 int bad_format(
4050     char *fmt)
4052     char     *ptr;
4053     int       n = 0;
4055     ptr = fmt;
4056     while (*ptr != '\0')
4057         if (*ptr++ == '%') {
4059             /* line cannot end with percent char */
4060             if (*ptr == '\0')
4061                 return 1;
4063             /* '%s', '%S' and '%%' are allowed */
4064             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4065                 ptr++;
4067             /* %c is allowed (but use only with vdef!) */
4068             else if (*ptr == 'c') {
4069                 ptr++;
4070                 n = 1;
4071             }
4073             /* or else '% 6.2lf' and such are allowed */
4074             else {
4075                 /* optional padding character */
4076                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4077                     ptr++;
4079                 /* This should take care of 'm.n' with all three optional */
4080                 while (*ptr >= '0' && *ptr <= '9')
4081                     ptr++;
4082                 if (*ptr == '.')
4083                     ptr++;
4084                 while (*ptr >= '0' && *ptr <= '9')
4085                     ptr++;
4087                 /* Either 'le', 'lf' or 'lg' must follow here */
4088                 if (*ptr++ != 'l')
4089                     return 1;
4090                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4091                     ptr++;
4092                 else
4093                     return 1;
4094                 n++;
4095             }
4096         }
4098     return (n != 1);
4102 int vdef_parse(
4103     gdes,
4104     str)
4105     struct graph_desc_t *gdes;
4106     const char *const str;
4108     /* A VDEF currently is either "func" or "param,func"
4109      * so the parsing is rather simple.  Change if needed.
4110      */
4111     double    param;
4112     char      func[30];
4113     int       n;
4115     n = 0;
4116     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4117     if (n == (int) strlen(str)) {   /* matched */
4118         ;
4119     } else {
4120         n = 0;
4121         sscanf(str, "%29[A-Z]%n", func, &n);
4122         if (n == (int) strlen(str)) {   /* matched */
4123             param = DNAN;
4124         } else {
4125             rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4126                           gdes->vname);
4127             return -1;
4128         }
4129     }
4130     if (!strcmp("PERCENT", func))
4131         gdes->vf.op = VDEF_PERCENT;
4132     else if (!strcmp("MAXIMUM", func))
4133         gdes->vf.op = VDEF_MAXIMUM;
4134     else if (!strcmp("AVERAGE", func))
4135         gdes->vf.op = VDEF_AVERAGE;
4136     else if (!strcmp("MINIMUM", func))
4137         gdes->vf.op = VDEF_MINIMUM;
4138     else if (!strcmp("TOTAL", func))
4139         gdes->vf.op = VDEF_TOTAL;
4140     else if (!strcmp("FIRST", func))
4141         gdes->vf.op = VDEF_FIRST;
4142     else if (!strcmp("LAST", func))
4143         gdes->vf.op = VDEF_LAST;
4144     else if (!strcmp("LSLSLOPE", func))
4145         gdes->vf.op = VDEF_LSLSLOPE;
4146     else if (!strcmp("LSLINT", func))
4147         gdes->vf.op = VDEF_LSLINT;
4148     else if (!strcmp("LSLCORREL", func))
4149         gdes->vf.op = VDEF_LSLCORREL;
4150     else {
4151         rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4152                       gdes->vname);
4153         return -1;
4154     };
4156     switch (gdes->vf.op) {
4157     case VDEF_PERCENT:
4158         if (isnan(param)) { /* no parameter given */
4159             rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4160                           func, gdes->vname);
4161             return -1;
4162         };
4163         if (param >= 0.0 && param <= 100.0) {
4164             gdes->vf.param = param;
4165             gdes->vf.val = DNAN;    /* undefined */
4166             gdes->vf.when = 0;  /* undefined */
4167         } else {
4168             rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4169                           gdes->vname);
4170             return -1;
4171         };
4172         break;
4173     case VDEF_MAXIMUM:
4174     case VDEF_AVERAGE:
4175     case VDEF_MINIMUM:
4176     case VDEF_TOTAL:
4177     case VDEF_FIRST:
4178     case VDEF_LAST:
4179     case VDEF_LSLSLOPE:
4180     case VDEF_LSLINT:
4181     case VDEF_LSLCORREL:
4182         if (isnan(param)) {
4183             gdes->vf.param = DNAN;
4184             gdes->vf.val = DNAN;
4185             gdes->vf.when = 0;
4186         } else {
4187             rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4188                           func, gdes->vname);
4189             return -1;
4190         };
4191         break;
4192     };
4193     return 0;
4197 int vdef_calc(
4198     im,
4199     gdi)
4200     image_desc_t *im;
4201     int gdi;
4203     graph_desc_t *src, *dst;
4204     rrd_value_t *data;
4205     long      step, steps;
4207     dst = &im->gdes[gdi];
4208     src = &im->gdes[dst->vidx];
4209     data = src->data + src->ds;
4210     steps = (src->end - src->start) / src->step;
4212 #if 0
4213     printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4214            src->end, steps);
4215 #endif
4217     switch (dst->vf.op) {
4218     case VDEF_PERCENT:{
4219         rrd_value_t *array;
4220         int       field;
4223         if ((array = malloc(steps * sizeof(double))) == NULL) {
4224             rrd_set_error("malloc VDEV_PERCENT");
4225             return -1;
4226         }
4227         for (step = 0; step < steps; step++) {
4228             array[step] = data[step * src->ds_cnt];
4229         }
4230         qsort(array, step, sizeof(double), vdef_percent_compar);
4232         field = (steps - 1) * dst->vf.param / 100;
4233         dst->vf.val = array[field];
4234         dst->vf.when = 0;   /* no time component */
4235         free(array);
4236 #if 0
4237         for (step = 0; step < steps; step++)
4238             printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4239                    step == field ? '*' : ' ');
4240 #endif
4241     }
4242         break;
4243     case VDEF_MAXIMUM:
4244         step = 0;
4245         while (step != steps && isnan(data[step * src->ds_cnt]))
4246             step++;
4247         if (step == steps) {
4248             dst->vf.val = DNAN;
4249             dst->vf.when = 0;
4250         } else {
4251             dst->vf.val = data[step * src->ds_cnt];
4252             dst->vf.when = src->start + (step + 1) * src->step;
4253         }
4254         while (step != steps) {
4255             if (finite(data[step * src->ds_cnt])) {
4256                 if (data[step * src->ds_cnt] > dst->vf.val) {
4257                     dst->vf.val = data[step * src->ds_cnt];
4258                     dst->vf.when = src->start + (step + 1) * src->step;
4259                 }
4260             }
4261             step++;
4262         }
4263         break;
4264     case VDEF_TOTAL:
4265     case VDEF_AVERAGE:{
4266         int       cnt = 0;
4267         double    sum = 0.0;
4269         for (step = 0; step < steps; step++) {
4270             if (finite(data[step * src->ds_cnt])) {
4271                 sum += data[step * src->ds_cnt];
4272                 cnt++;
4273             };
4274         }
4275         if (cnt) {
4276             if (dst->vf.op == VDEF_TOTAL) {
4277                 dst->vf.val = sum * src->step;
4278                 dst->vf.when = 0;   /* no time component */
4279             } else {
4280                 dst->vf.val = sum / cnt;
4281                 dst->vf.when = 0;   /* no time component */
4282             };
4283         } else {
4284             dst->vf.val = DNAN;
4285             dst->vf.when = 0;
4286         }
4287     }
4288         break;
4289     case VDEF_MINIMUM:
4290         step = 0;
4291         while (step != steps && isnan(data[step * src->ds_cnt]))
4292             step++;
4293         if (step == steps) {
4294             dst->vf.val = DNAN;
4295             dst->vf.when = 0;
4296         } else {
4297             dst->vf.val = data[step * src->ds_cnt];
4298             dst->vf.when = src->start + (step + 1) * src->step;
4299         }
4300         while (step != steps) {
4301             if (finite(data[step * src->ds_cnt])) {
4302                 if (data[step * src->ds_cnt] < dst->vf.val) {
4303                     dst->vf.val = data[step * src->ds_cnt];
4304                     dst->vf.when = src->start + (step + 1) * src->step;
4305                 }
4306             }
4307             step++;
4308         }
4309         break;
4310     case VDEF_FIRST:
4311         /* The time value returned here is one step before the
4312          * actual time value.  This is the start of the first
4313          * non-NaN interval.
4314          */
4315         step = 0;
4316         while (step != steps && isnan(data[step * src->ds_cnt]))
4317             step++;
4318         if (step == steps) {    /* all entries were NaN */
4319             dst->vf.val = DNAN;
4320             dst->vf.when = 0;
4321         } else {
4322             dst->vf.val = data[step * src->ds_cnt];
4323             dst->vf.when = src->start + step * src->step;
4324         }
4325         break;
4326     case VDEF_LAST:
4327         /* The time value returned here is the
4328          * actual time value.  This is the end of the last
4329          * non-NaN interval.
4330          */
4331         step = steps - 1;
4332         while (step >= 0 && isnan(data[step * src->ds_cnt]))
4333             step--;
4334         if (step < 0) { /* all entries were NaN */
4335             dst->vf.val = DNAN;
4336             dst->vf.when = 0;
4337         } else {
4338             dst->vf.val = data[step * src->ds_cnt];
4339             dst->vf.when = src->start + (step + 1) * src->step;
4340         }
4341         break;
4342     case VDEF_LSLSLOPE:
4343     case VDEF_LSLINT:
4344     case VDEF_LSLCORREL:{
4345         /* Bestfit line by linear least squares method */
4347         int       cnt = 0;
4348         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4350         SUMx = 0;
4351         SUMy = 0;
4352         SUMxy = 0;
4353         SUMxx = 0;
4354         SUMyy = 0;
4356         for (step = 0; step < steps; step++) {
4357             if (finite(data[step * src->ds_cnt])) {
4358                 cnt++;
4359                 SUMx += step;
4360                 SUMxx += step * step;
4361                 SUMxy += step * data[step * src->ds_cnt];
4362                 SUMy += data[step * src->ds_cnt];
4363                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4364             };
4365         }
4367         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4368         y_intercept = (SUMy - slope * SUMx) / cnt;
4369         correl =
4370             (SUMxy -
4371              (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4372                                           (SUMx * SUMx) / cnt) * (SUMyy -
4373                                                                   (SUMy *
4374                                                                    SUMy) /
4375                                                                   cnt));
4377         if (cnt) {
4378             if (dst->vf.op == VDEF_LSLSLOPE) {
4379                 dst->vf.val = slope;
4380                 dst->vf.when = 0;
4381             } else if (dst->vf.op == VDEF_LSLINT) {
4382                 dst->vf.val = y_intercept;
4383                 dst->vf.when = 0;
4384             } else if (dst->vf.op == VDEF_LSLCORREL) {
4385                 dst->vf.val = correl;
4386                 dst->vf.when = 0;
4387             };
4389         } else {
4390             dst->vf.val = DNAN;
4391             dst->vf.when = 0;
4392         }
4393     }
4394         break;
4395     }
4396     return 0;
4399 /* NaN < -INF < finite_values < INF */
4400 int vdef_percent_compar(
4401     a,
4402     b)
4403     const void *a, *b;
4405     /* Equality is not returned; this doesn't hurt except
4406      * (maybe) for a little performance.
4407      */
4409     /* First catch NaN values. They are smallest */
4410     if (isnan(*(double *) a))
4411         return -1;
4412     if (isnan(*(double *) b))
4413         return 1;
4415     /* NaN doesn't reach this part so INF and -INF are extremes.
4416      * The sign from isinf() is compatible with the sign we return
4417      */
4418     if (isinf(*(double *) a))
4419         return isinf(*(double *) a);
4420     if (isinf(*(double *) b))
4421         return isinf(*(double *) b);
4423     /* If we reach this, both values must be finite */
4424     if (*(double *) a < *(double *) b)
4425         return -1;
4426     else
4427         return 1;