Code

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