Code

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