Code

make sure an extra %s in the secondi axis format does not kill us
[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             if (ft_step < im->gdes[i].step) {
859                 reduce_data(im->gdes[i].cf_reduce,
860                             ft_step,
861                             &im->gdes[i].start,
862                             &im->gdes[i].end,
863                             &im->gdes[i].step,
864                             &im->gdes[i].ds_cnt, &im->gdes[i].data);
865             } else {
866                 im->gdes[i].step = ft_step;
867             }
868         }
870         /* lets see if the required data source is really there */
871         for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
872             if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
873                 im->gdes[i].ds = ii;
874             }
875         }
876         if (im->gdes[i].ds == -1) {
877             rrd_set_error("No DS called '%s' in '%s'",
878                           im->gdes[i].ds_nam, im->gdes[i].rrd);
879             return -1;
880         }
882     }
883     return 0;
886 /* evaluate the expressions in the CDEF functions */
888 /*************************************************************
889  * CDEF stuff 
890  *************************************************************/
892 long find_var_wrapper(
893     void *arg1,
894     char *key)
896     return find_var((image_desc_t *) arg1, key);
899 /* find gdes containing var*/
900 long find_var(
901     image_desc_t *im,
902     char *key)
904     long      ii;
906     for (ii = 0; ii < im->gdes_c - 1; ii++) {
907         if ((im->gdes[ii].gf == GF_DEF
908              || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
909             && (strcmp(im->gdes[ii].vname, key) == 0)) {
910             return ii;
911         }
912     }
913     return -1;
916 /* find the largest common denominator for all the numbers
917    in the 0 terminated num array */
918 long lcd(
919     long *num)
921     long      rest;
922     int       i;
924     for (i = 0; num[i + 1] != 0; i++) {
925         do {
926             rest = num[i] % num[i + 1];
927             num[i] = num[i + 1];
928             num[i + 1] = rest;
929         } while (rest != 0);
930         num[i + 1] = num[i];
931     }
932 /*    return i==0?num[i]:num[i-1]; */
933     return num[i];
936 /* run the rpn calculator on all the VDEF and CDEF arguments */
937 int data_calc(
938     image_desc_t *im)
941     int       gdi;
942     int       dataidx;
943     long     *steparray, rpi;
944     int       stepcnt;
945     time_t    now;
946     rpnstack_t rpnstack;
948     rpnstack_init(&rpnstack);
950     for (gdi = 0; gdi < im->gdes_c; gdi++) {
951         /* Look for GF_VDEF and GF_CDEF in the same loop,
952          * so CDEFs can use VDEFs and vice versa
953          */
954         switch (im->gdes[gdi].gf) {
955         case GF_XPORT:
956             break;
957         case GF_SHIFT:{
958             graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
960             /* remove current shift */
961             vdp->start -= vdp->shift;
962             vdp->end -= vdp->shift;
964             /* vdef */
965             if (im->gdes[gdi].shidx >= 0)
966                 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
967             /* constant */
968             else
969                 vdp->shift = im->gdes[gdi].shval;
971             /* normalize shift to multiple of consolidated step */
972             vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
974             /* apply shift */
975             vdp->start += vdp->shift;
976             vdp->end += vdp->shift;
977             break;
978         }
979         case GF_VDEF:
980             /* A VDEF has no DS.  This also signals other parts
981              * of rrdtool that this is a VDEF value, not a CDEF.
982              */
983             im->gdes[gdi].ds_cnt = 0;
984             if (vdef_calc(im, gdi)) {
985                 rrd_set_error("Error processing VDEF '%s'",
986                               im->gdes[gdi].vname);
987                 rpnstack_free(&rpnstack);
988                 return -1;
989             }
990             break;
991         case GF_CDEF:
992             im->gdes[gdi].ds_cnt = 1;
993             im->gdes[gdi].ds = 0;
994             im->gdes[gdi].data_first = 1;
995             im->gdes[gdi].start = 0;
996             im->gdes[gdi].end = 0;
997             steparray = NULL;
998             stepcnt = 0;
999             dataidx = -1;
1001             /* Find the variables in the expression.
1002              * - VDEF variables are substituted by their values
1003              *   and the opcode is changed into OP_NUMBER.
1004              * - CDEF variables are analized for their step size,
1005              *   the lowest common denominator of all the step
1006              *   sizes of the data sources involved is calculated
1007              *   and the resulting number is the step size for the
1008              *   resulting data source.
1009              */
1010             for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1011                 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1012                     im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1013                     long      ptr = im->gdes[gdi].rpnp[rpi].ptr;
1015                     if (im->gdes[ptr].ds_cnt == 0) {    /* this is a VDEF data source */
1016 #if 0
1017                         printf
1018                             ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1019                              im->gdes[gdi].vname, im->gdes[ptr].vname);
1020                         printf("DEBUG: value from vdef is %f\n",
1021                                im->gdes[ptr].vf.val);
1022 #endif
1023                         im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1024                         im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1025                     } else {    /* normal variables and PREF(variables) */
1027                         /* add one entry to the array that keeps track of the step sizes of the
1028                          * data sources going into the CDEF. */
1029                         if ((steparray =
1030                                          (long*)(rrd_realloc(steparray,
1031                                          (++stepcnt +
1032                                           1) * sizeof(*steparray)))) == NULL) {
1033                             rrd_set_error("realloc steparray");
1034                             rpnstack_free(&rpnstack);
1035                             return -1;
1036                         };
1038                         steparray[stepcnt - 1] = im->gdes[ptr].step;
1040                         /* adjust start and end of cdef (gdi) so
1041                          * that it runs from the latest start point
1042                          * to the earliest endpoint of any of the
1043                          * rras involved (ptr)
1044                          */
1046                         if (im->gdes[gdi].start < im->gdes[ptr].start)
1047                             im->gdes[gdi].start = im->gdes[ptr].start;
1049                         if (im->gdes[gdi].end == 0 ||
1050                             im->gdes[gdi].end > im->gdes[ptr].end)
1051                             im->gdes[gdi].end = im->gdes[ptr].end;
1053                         /* store pointer to the first element of
1054                          * the rra providing data for variable,
1055                          * further save step size and data source
1056                          * count of this rra
1057                          */
1058                         im->gdes[gdi].rpnp[rpi].data =
1059                             im->gdes[ptr].data + im->gdes[ptr].ds;
1060                         im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1061                         im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1063                         /* backoff the *.data ptr; this is done so
1064                          * rpncalc() function doesn't have to treat
1065                          * the first case differently
1066                          */
1067                     }   /* if ds_cnt != 0 */
1068                 }       /* if OP_VARIABLE */
1069             }           /* loop through all rpi */
1071             /* move the data pointers to the correct period */
1072             for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1073                 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1074                     im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1075                     long      ptr = im->gdes[gdi].rpnp[rpi].ptr;
1076                     long      diff =
1077                         im->gdes[gdi].start - im->gdes[ptr].start;
1079                     if (diff > 0)
1080                         im->gdes[gdi].rpnp[rpi].data +=
1081                             (diff / im->gdes[ptr].step) *
1082                             im->gdes[ptr].ds_cnt;
1083                 }
1084             }
1086             if (steparray == NULL) {
1087                 rrd_set_error("rpn expressions without DEF"
1088                               " or CDEF variables are not supported");
1089                 rpnstack_free(&rpnstack);
1090                 return -1;
1091             }
1092             steparray[stepcnt] = 0;
1093             /* Now find the resulting step.  All steps in all
1094              * used RRAs have to be visited
1095              */
1096             im->gdes[gdi].step = lcd(steparray);
1097             free(steparray);
1098             if ((im->gdes[gdi].data = (rrd_value_t*)(malloc(((im->gdes[gdi].end -
1099                                                im->gdes[gdi].start)
1100                                               / im->gdes[gdi].step)
1101                                              * sizeof(double)))) == NULL) {
1102                 rrd_set_error("malloc im->gdes[gdi].data");
1103                 rpnstack_free(&rpnstack);
1104                 return -1;
1105             }
1107             /* Step through the new cdef results array and
1108              * calculate the values
1109              */
1110             for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1111                  now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1112                 rpnp_t   *rpnp = im->gdes[gdi].rpnp;
1114                 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1115                  * in this case we are advancing by timesteps;
1116                  * we use the fact that time_t is a synonym for long
1117                  */
1118                 if (rpn_calc(rpnp, &rpnstack, (long) now,
1119                              im->gdes[gdi].data, ++dataidx) == -1) {
1120                     /* rpn_calc sets the error string */
1121                     rpnstack_free(&rpnstack);
1122                     return -1;
1123                 }
1124             }           /* enumerate over time steps within a CDEF */
1125             break;
1126         default:
1127             continue;
1128         }
1129     }                   /* enumerate over CDEFs */
1130     rpnstack_free(&rpnstack);
1131     return 0;
1134 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1135 /* yes we are loosing precision by doing tos with floats instead of doubles
1136    but it seems more stable this way. */
1138 static int AlmostEqual2sComplement(
1139     float A,
1140     float B,
1141     int maxUlps)
1144     int       aInt = *(int *) &A;
1145     int       bInt = *(int *) &B;
1146     int       intDiff;
1148     /* Make sure maxUlps is non-negative and small enough that the
1149        default NAN won't compare as equal to anything.  */
1151     /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1153     /* Make aInt lexicographically ordered as a twos-complement int */
1155     if (aInt < 0)
1156         aInt = 0x80000000l - aInt;
1158     /* Make bInt lexicographically ordered as a twos-complement int */
1160     if (bInt < 0)
1161         bInt = 0x80000000l - bInt;
1163     intDiff = abs(aInt - bInt);
1165     if (intDiff <= maxUlps)
1166         return 1;
1168     return 0;
1171 /* massage data so, that we get one value for each x coordinate in the graph */
1172 int data_proc(
1173     image_desc_t *im)
1175     long      i, ii;
1176     double    pixstep = (double) (im->end - im->start)
1177         / (double) im->xsize;   /* how much time 
1178                                    passes in one pixel */
1179     double    paintval;
1180     double    minval = DNAN, maxval = DNAN;
1182     unsigned long gr_time;
1184     /* memory for the processed data */
1185     for (i = 0; i < im->gdes_c; i++) {
1186         if ((im->gdes[i].gf == GF_LINE) ||
1187             (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1188             if ((im->gdes[i].p_data = (rrd_value_t*)(malloc((im->xsize + 1)
1189                                              * sizeof(rrd_value_t)))) == NULL) {
1190                 rrd_set_error("malloc data_proc");
1191                 return -1;
1192             }
1193         }
1194     }
1196     for (i = 0; i < im->xsize; i++) {   /* for each pixel */
1197         long      vidx;
1199         gr_time = im->start + pixstep * i;  /* time of the current step */
1200         paintval = 0.0;
1202         for (ii = 0; ii < im->gdes_c; ii++) {
1203             double    value;
1205             switch (im->gdes[ii].gf) {
1206             case GF_LINE:
1207             case GF_AREA:
1208             case GF_TICK:
1209                 if (!im->gdes[ii].stack)
1210                     paintval = 0.0;
1211                 value = im->gdes[ii].yrule;
1212                 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1213                     /* The time of the data doesn't necessarily match
1214                      ** the time of the graph. Beware.
1215                      */
1216                     vidx = im->gdes[ii].vidx;
1217                     if (im->gdes[vidx].gf == GF_VDEF) {
1218                         value = im->gdes[vidx].vf.val;
1219                     } else
1220                         if (((long int) gr_time >=
1221                              (long int) im->gdes[vidx].start)
1222                             && ((long int) gr_time <
1223                                 (long int) im->gdes[vidx].end)) {
1224                         value = im->gdes[vidx].data[(unsigned long)
1225                                                     floor((double)
1226                                                           (gr_time -
1227                                                            im->gdes[vidx].
1228                                                            start)
1229                                                           /
1230                                                           im->gdes[vidx].step)
1231                                                     * im->gdes[vidx].ds_cnt +
1232                                                     im->gdes[vidx].ds];
1233                     } else {
1234                         value = DNAN;
1235                     }
1236                 };
1238                 if (!isnan(value)) {
1239                     paintval += value;
1240                     im->gdes[ii].p_data[i] = paintval;
1241                     /* GF_TICK: the data values are not
1242                      ** relevant for min and max
1243                      */
1244                     if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1245                         if ((isnan(minval) || paintval < minval) &&
1246                             !(im->logarithmic && paintval <= 0.0))
1247                             minval = paintval;
1248                         if (isnan(maxval) || paintval > maxval)
1249                             maxval = paintval;
1250                     }
1251                 } else {
1252                     im->gdes[ii].p_data[i] = DNAN;
1253                 }
1254                 break;
1255             case GF_STACK:
1256                 rrd_set_error
1257                     ("STACK should already be turned into LINE or AREA here");
1258                 return -1;
1259                 break;
1260             default:
1261                 break;
1262             }
1263         }
1264     }
1266     /* if min or max have not been asigned a value this is because
1267        there was no data in the graph ... this is not good ...
1268        lets set these to dummy values then ... */
1270     if (im->logarithmic) {
1271         if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1272             minval = 0.0;   /* catching this right away below */
1273             maxval = 5.1;
1274         }
1275         /* in logarithm mode, where minval is smaller or equal 
1276            to 0 make the beast just way smaller than maxval */
1277         if (minval <= 0) {
1278             minval = maxval / 10e8;
1279         }
1280     } else {
1281         if (isnan(minval) || isnan(maxval)) {
1282             minval = 0.0;
1283             maxval = 1.0;
1284         }
1285     }
1287     /* adjust min and max values given by the user */
1288     /* for logscale we add something on top */
1289     if (isnan(im->minval)
1290         || ((!im->rigid) && im->minval > minval)
1291         ) {
1292         if (im->logarithmic)
1293             im->minval = minval / 2.0;
1294         else
1295             im->minval = minval;
1296     }
1297     if (isnan(im->maxval)
1298         || (!im->rigid && im->maxval < maxval)
1299         ) {
1300         if (im->logarithmic)
1301             im->maxval = maxval * 2.0;
1302         else
1303             im->maxval = maxval;
1304     }
1306     /* make sure min is smaller than max */
1307     if (im->minval > im->maxval) {
1308         if (im->minval > 0)
1309             im->minval = 0.99 * im->maxval;
1310         else
1311             im->minval = 1.01 * im->maxval;
1312     }
1314     /* make sure min and max are not equal */
1315     if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1316         if (im->maxval > 0)
1317             im->maxval *= 1.01;
1318         else
1319             im->maxval *= 0.99;
1321         /* make sure min and max are not both zero */
1322         if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1323             im->maxval = 1.0;
1324         }
1325     }
1326     return 0;
1331 /* identify the point where the first gridline, label ... gets placed */
1333 time_t find_first_time(
1334     time_t start,       /* what is the initial time */
1335     enum tmt_en baseint,    /* what is the basic interval */
1336     long basestep       /* how many if these do we jump a time */
1337     )
1339     struct tm tm;
1341     localtime_r(&start, &tm);
1343     switch (baseint) {
1344     case TMT_SECOND:
1345         tm.       tm_sec -= tm.tm_sec % basestep;
1347         break;
1348     case TMT_MINUTE:
1349         tm.       tm_sec = 0;
1350         tm.       tm_min -= tm.tm_min % basestep;
1352         break;
1353     case TMT_HOUR:
1354         tm.       tm_sec = 0;
1355         tm.       tm_min = 0;
1356         tm.       tm_hour -= tm.tm_hour % basestep;
1358         break;
1359     case TMT_DAY:
1360         /* we do NOT look at the basestep for this ... */
1361         tm.       tm_sec = 0;
1362         tm.       tm_min = 0;
1363         tm.       tm_hour = 0;
1365         break;
1366     case TMT_WEEK:
1367         /* we do NOT look at the basestep for this ... */
1368         tm.       tm_sec = 0;
1369         tm.       tm_min = 0;
1370         tm.       tm_hour = 0;
1371         tm.       tm_mday -= tm.tm_wday - 1;    /* -1 because we want the monday */
1373         if (tm.tm_wday == 0)
1374             tm.       tm_mday -= 7; /* we want the *previous* monday */
1376         break;
1377     case TMT_MONTH:
1378         tm.       tm_sec = 0;
1379         tm.       tm_min = 0;
1380         tm.       tm_hour = 0;
1381         tm.       tm_mday = 1;
1382         tm.       tm_mon -= tm.tm_mon % basestep;
1384         break;
1386     case TMT_YEAR:
1387         tm.       tm_sec = 0;
1388         tm.       tm_min = 0;
1389         tm.       tm_hour = 0;
1390         tm.       tm_mday = 1;
1391         tm.       tm_mon = 0;
1392         tm.       tm_year -= (
1393     tm.tm_year + 1900) %basestep;
1395     }
1396     return mktime(&tm);
1399 /* identify the point where the next gridline, label ... gets placed */
1400 time_t find_next_time(
1401     time_t current,     /* what is the initial time */
1402     enum tmt_en baseint,    /* what is the basic interval */
1403     long basestep       /* how many if these do we jump a time */
1404     )
1406     struct tm tm;
1407     time_t    madetime;
1409     localtime_r(&current, &tm);
1411     do {
1412         switch (baseint) {
1413         case TMT_SECOND:
1414             tm.       tm_sec += basestep;
1416             break;
1417         case TMT_MINUTE:
1418             tm.       tm_min += basestep;
1420             break;
1421         case TMT_HOUR:
1422             tm.       tm_hour += basestep;
1424             break;
1425         case TMT_DAY:
1426             tm.       tm_mday += basestep;
1428             break;
1429         case TMT_WEEK:
1430             tm.       tm_mday += 7 * basestep;
1432             break;
1433         case TMT_MONTH:
1434             tm.       tm_mon += basestep;
1436             break;
1437         case TMT_YEAR:
1438             tm.       tm_year += basestep;
1439         }
1440         madetime = mktime(&tm);
1441     } while (madetime == -1);   /* this is necessary to skip impssible times
1442                                    like the daylight saving time skips */
1443     return madetime;
1448 /* calculate values required for PRINT and GPRINT functions */
1450 int print_calc(
1451     image_desc_t *im)
1453     long      i, ii, validsteps;
1454     double    printval;
1455     struct tm tmvdef;
1456     int       graphelement = 0;
1457     long      vidx;
1458     int       max_ii;
1459     double    magfact = -1;
1460     char     *si_symb = "";
1461     char     *percent_s;
1462     int       prline_cnt = 0;
1464     /* wow initializing tmvdef is quite a task :-) */
1465     time_t    now = time(NULL);
1467     localtime_r(&now, &tmvdef);
1468     for (i = 0; i < im->gdes_c; i++) {
1469         vidx = im->gdes[i].vidx;
1470         switch (im->gdes[i].gf) {
1471         case GF_PRINT:
1472         case GF_GPRINT:
1473             /* PRINT and GPRINT can now print VDEF generated values.
1474              * There's no need to do any calculations on them as these
1475              * calculations were already made.
1476              */
1477             if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1478                 printval = im->gdes[vidx].vf.val;
1479                 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1480             } else {    /* need to calculate max,min,avg etcetera */
1481                 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1482                           / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1483                 printval = DNAN;
1484                 validsteps = 0;
1485                 for (ii = im->gdes[vidx].ds;
1486                      ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1487                     if (!finite(im->gdes[vidx].data[ii]))
1488                         continue;
1489                     if (isnan(printval)) {
1490                         printval = im->gdes[vidx].data[ii];
1491                         validsteps++;
1492                         continue;
1493                     }
1495                     switch (im->gdes[i].cf) {
1496                     case CF_HWPREDICT:
1497                     case CF_MHWPREDICT:
1498                     case CF_DEVPREDICT:
1499                     case CF_DEVSEASONAL:
1500                     case CF_SEASONAL:
1501                     case CF_AVERAGE:
1502                         validsteps++;
1503                         printval += im->gdes[vidx].data[ii];
1504                         break;
1505                     case CF_MINIMUM:
1506                         printval = min(printval, im->gdes[vidx].data[ii]);
1507                         break;
1508                     case CF_FAILURES:
1509                     case CF_MAXIMUM:
1510                         printval = max(printval, im->gdes[vidx].data[ii]);
1511                         break;
1512                     case CF_LAST:
1513                         printval = im->gdes[vidx].data[ii];
1514                     }
1515                 }
1516                 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1517                     if (validsteps > 1) {
1518                         printval = (printval / validsteps);
1519                     }
1520                 }
1521             }           /* prepare printval */
1523             if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1524                 /* Magfact is set to -1 upon entry to print_calc.  If it
1525                  * is still less than 0, then we need to run auto_scale.
1526                  * Otherwise, put the value into the correct units.  If
1527                  * the value is 0, then do not set the symbol or magnification
1528                  * so next the calculation will be performed again. */
1529                 if (magfact < 0.0) {
1530                     auto_scale(im, &printval, &si_symb, &magfact);
1531                     if (printval == 0.0)
1532                         magfact = -1.0;
1533                 } else {
1534                     printval /= magfact;
1535                 }
1536                 *(++percent_s) = 's';
1537             } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1538                 auto_scale(im, &printval, &si_symb, &magfact);
1539             }
1541             if (im->gdes[i].gf == GF_PRINT) {
1542                 rrd_infoval_t prline;
1544                 if (im->gdes[i].strftm) {
1545                     prline.u_str = (char*)(malloc((FMT_LEG_LEN + 2) * sizeof(char)));
1546                     strftime(prline.u_str,
1547                              FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1548                 } else if (bad_format(im->gdes[i].format)) {
1549                     rrd_set_error
1550                         ("bad format for PRINT in '%s'", im->gdes[i].format);
1551                     return -1;
1552                 } else {
1553                     prline.u_str =
1554                         sprintf_alloc(im->gdes[i].format, printval, si_symb);
1555                 }
1556                 grinfo_push(im,
1557                             sprintf_alloc
1558                             ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1559                 free(prline.u_str);
1560             } else {
1561                 /* GF_GPRINT */
1563                 if (im->gdes[i].strftm) {
1564                     strftime(im->gdes[i].legend,
1565                              FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1566                 } else {
1567                     if (bad_format(im->gdes[i].format)) {
1568                         rrd_set_error
1569                             ("bad format for GPRINT in '%s'",
1570                              im->gdes[i].format);
1571                         return -1;
1572                     }
1573 #ifdef HAVE_SNPRINTF
1574                     snprintf(im->gdes[i].legend,
1575                              FMT_LEG_LEN - 2,
1576                              im->gdes[i].format, printval, si_symb);
1577 #else
1578                     sprintf(im->gdes[i].legend,
1579                             im->gdes[i].format, printval, si_symb);
1580 #endif
1581                 }
1582                 graphelement = 1;
1583             }
1584             break;
1585         case GF_LINE:
1586         case GF_AREA:
1587         case GF_TICK:
1588             graphelement = 1;
1589             break;
1590         case GF_HRULE:
1591             if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1592                 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1593             };
1594             graphelement = 1;
1595             break;
1596         case GF_VRULE:
1597             if (im->gdes[i].xrule == 0) {   /* again ... the legend printer needs it */
1598                 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1599             };
1600             graphelement = 1;
1601             break;
1602         case GF_COMMENT:
1603         case GF_TEXTALIGN:
1604         case GF_DEF:
1605         case GF_CDEF:
1606         case GF_VDEF:
1607 #ifdef WITH_PIECHART
1608         case GF_PART:
1609 #endif
1610         case GF_SHIFT:
1611         case GF_XPORT:
1612             break;
1613         case GF_STACK:
1614             rrd_set_error
1615                 ("STACK should already be turned into LINE or AREA here");
1616             return -1;
1617             break;
1618         }
1619     }
1620     return graphelement;
1624 /* place legends with color spots */
1625 int leg_place(
1626     image_desc_t *im,
1627     int *gY)
1629     /* graph labels */
1630     int       interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1631     int       border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1632     int       fill = 0, fill_last;
1633     int       leg_c = 0;
1634     double    leg_x = border;
1635     int       leg_y = im->yimg;
1636     int       leg_y_prev = im->yimg;
1637     int       leg_cc;
1638     double    glue = 0;
1639     int       i, ii, mark = 0;
1640     char      default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1641     int      *legspace;
1642     char     *tab;
1644     if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1645         if ((legspace = (int*)(malloc(im->gdes_c * sizeof(int)))) == NULL) {
1646             rrd_set_error("malloc for legspace");
1647             return -1;
1648         }
1650         for (i = 0; i < im->gdes_c; i++) {
1651             char      prt_fctn; /*special printfunctions */
1652             fill_last = fill;
1653             /* hide legends for rules which are not displayed */
1654             if (im->gdes[i].gf == GF_TEXTALIGN) {
1655                 default_txtalign = im->gdes[i].txtalign;
1656             }
1658             if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1659                 if (im->gdes[i].gf == GF_HRULE
1660                     && (im->gdes[i].yrule <
1661                         im->minval || im->gdes[i].yrule > im->maxval))
1662                     im->gdes[i].legend[0] = '\0';
1663                 if (im->gdes[i].gf == GF_VRULE
1664                     && (im->gdes[i].xrule <
1665                         im->start || im->gdes[i].xrule > im->end))
1666                     im->gdes[i].legend[0] = '\0';
1667             }
1669             /* turn \\t into tab */
1670             while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1671                 memmove(tab, tab + 1, strlen(tab));
1672                 tab[0] = (char) 9;
1673             }
1674             leg_cc = strlen(im->gdes[i].legend);
1675             /* is there a controle code at the end of the legend string ? */
1676             if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1677                 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1678                 leg_cc -= 2;
1679                 im->gdes[i].legend[leg_cc] = '\0';
1680             } else {
1681                 prt_fctn = '\0';
1682             }
1683             /* only valid control codes */
1684             if (prt_fctn != 'l' && prt_fctn != 'n' &&   /* a synonym for l */
1685                 prt_fctn != 'r' &&
1686                 prt_fctn != 'j' &&
1687                 prt_fctn != 'c' &&
1688                 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1689                 free(legspace);
1690                 rrd_set_error
1691                     ("Unknown control code at the end of '%s\\%c'",
1692                      im->gdes[i].legend, prt_fctn);
1693                 return -1;
1694             }
1695             /* \n -> \l */
1696             if (prt_fctn == 'n') {
1697                 prt_fctn = 'l';
1698             }
1700             /* remove exess space from the end of the legend for \g */
1701             while (prt_fctn == 'g' &&
1702                    leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1703                 leg_cc--;
1704                 im->gdes[i].legend[leg_cc] = '\0';
1705             }
1707             if (leg_cc != 0) {
1709                 /* no interleg space if string ends in \g */
1710                 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1711                 if (fill > 0) {
1712                     fill += legspace[i];
1713                 }
1714                 fill +=
1715                     gfx_get_text_width(im,
1716                                        fill + border,
1717                                        im->
1718                                        text_prop
1719                                        [TEXT_PROP_LEGEND].
1720                                        font_desc,
1721                                        im->tabwidth, im->gdes[i].legend);
1722                 leg_c++;
1723             } else {
1724                 legspace[i] = 0;
1725             }
1726             /* who said there was a special tag ... ? */
1727             if (prt_fctn == 'g') {
1728                 prt_fctn = '\0';
1729             }
1731             if (prt_fctn == '\0') {
1732                 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1733                     /* just one legend item is left right or center */
1734                     switch (default_txtalign) {
1735                     case TXA_RIGHT:
1736                         prt_fctn = 'r';
1737                         break;
1738                     case TXA_CENTER:
1739                         prt_fctn = 'c';
1740                         break;
1741                     case TXA_JUSTIFIED:
1742                         prt_fctn = 'j';
1743                         break;
1744                     default:
1745                         prt_fctn = 'l';
1746                         break;
1747                     }
1748                 }
1749                 /* is it time to place the legends ? */
1750                 if (fill > im->ximg - 2 * border) {
1751                     if (leg_c > 1) {
1752                         /* go back one */
1753                         i--;
1754                         fill = fill_last;
1755                         leg_c--;
1756                     }
1757                 }
1758                 if (leg_c == 1 && prt_fctn == 'j') {
1759                     prt_fctn = 'l';
1760                 }
1761             }
1764             if (prt_fctn != '\0') {
1765                 leg_x = border;
1766                 if (leg_c >= 2 && prt_fctn == 'j') {
1767                     glue = (double)(im->ximg - fill - 2 * border) / (double)(leg_c - 1);
1768                 } else {
1769                     glue = 0;
1770                 }
1771                 if (prt_fctn == 'c')
1772                     leg_x = (double)(im->ximg - fill) / 2.0;
1773                 if (prt_fctn == 'r')
1774                     leg_x = im->ximg - fill - border;
1775                 for (ii = mark; ii <= i; ii++) {
1776                     if (im->gdes[ii].legend[0] == '\0')
1777                         continue;   /* skip empty legends */
1778                     im->gdes[ii].leg_x = leg_x;
1779                     im->gdes[ii].leg_y = leg_y;
1780                     leg_x +=
1781                         (double)gfx_get_text_width(im, leg_x,
1782                                            im->
1783                                            text_prop
1784                                            [TEXT_PROP_LEGEND].
1785                                            font_desc,
1786                                            im->tabwidth, im->gdes[ii].legend)
1787                         +(double)legspace[ii]
1788                         + glue;
1789                 }
1790                 leg_y_prev = leg_y;
1791                 if (leg_x > border || prt_fctn == 's')
1792                     leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1793                 if (prt_fctn == 's')
1794                     leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1795                 fill = 0;
1796                 leg_c = 0;
1797                 mark = ii;
1798             }
1799         }
1801         if (im->extra_flags & FULL_SIZE_MODE) {
1802             /* now for some backpaddeling. We have to shift up all the
1803                legend items into the graph and tell the caller about the
1804                space we used up. */
1805             long shift_up = leg_y - im->yimg - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 + border * 0.7;
1806             for (i = 0; i < im->gdes_c; i++) {
1807                 im->gdes[i].leg_y -= shift_up;
1808             }
1809             im->yorigin = im->yorigin - leg_y + im->yimg - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 - border;            
1810             *gY = im->yorigin;
1811         } else {
1812             im->yimg =
1813                 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8 +
1814                 border * 0.6;
1815         }
1816         free(legspace);
1817     }
1818     return 0;
1821 /* create a grid on the graph. it determines what to do
1822    from the values of xsize, start and end */
1824 /* the xaxis labels are determined from the number of seconds per pixel
1825    in the requested graph */
1827 int calc_horizontal_grid(
1828     image_desc_t
1829     *im)
1831     double    range;
1832     double    scaledrange;
1833     int       pixel, i;
1834     int       gridind = 0;
1835     int       decimals, fractionals;
1837     im->ygrid_scale.labfact = 2;
1838     range = im->maxval - im->minval;
1839     scaledrange = range / im->magfact;
1840     /* does the scale of this graph make it impossible to put lines
1841        on it? If so, give up. */
1842     if (isnan(scaledrange)) {
1843         return 0;
1844     }
1846     /* find grid spaceing */
1847     pixel = 1;
1848     if (isnan(im->ygridstep)) {
1849         if (im->extra_flags & ALTYGRID) {
1850             /* find the value with max number of digits. Get number of digits */
1851             decimals =
1852                 ceil(log10
1853                      (max(fabs(im->maxval), fabs(im->minval)) *
1854                       im->viewfactor / im->magfact));
1855             if (decimals <= 0)  /* everything is small. make place for zero */
1856                 decimals = 1;
1857             im->ygrid_scale.gridstep =
1858                 pow((double) 10,
1859                     floor(log10(range * im->viewfactor / im->magfact))) /
1860                 im->viewfactor * im->magfact;
1861             if (im->ygrid_scale.gridstep == 0)  /* range is one -> 0.1 is reasonable scale */
1862                 im->ygrid_scale.gridstep = 0.1;
1863             /* should have at least 5 lines but no more then 15 */
1864             if (range / im->ygrid_scale.gridstep < 5
1865                 && im->ygrid_scale.gridstep >= 30)
1866                 im->ygrid_scale.gridstep /= 10;
1867             if (range / im->ygrid_scale.gridstep > 15)
1868                 im->ygrid_scale.gridstep *= 10;
1869             if (range / im->ygrid_scale.gridstep > 5) {
1870                 im->ygrid_scale.labfact = 1;
1871                 if (range / im->ygrid_scale.gridstep > 8
1872                     || im->ygrid_scale.gridstep <
1873                     1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1874                     im->ygrid_scale.labfact = 2;
1875             } else {
1876                 im->ygrid_scale.gridstep /= 5;
1877                 im->ygrid_scale.labfact = 5;
1878             }
1879             fractionals =
1880                 floor(log10
1881                       (im->ygrid_scale.gridstep *
1882                        (double) im->ygrid_scale.labfact * im->viewfactor /
1883                        im->magfact));
1884             if (fractionals < 0) {  /* small amplitude. */
1885                 int       len = decimals - fractionals + 1;
1887                 if (im->unitslength < len + 2)
1888                     im->unitslength = len + 2;
1889                 sprintf(im->ygrid_scale.labfmt,
1890                         "%%%d.%df%s", len,
1891                         -fractionals, (im->symbol != ' ' ? " %c" : ""));
1892             } else {
1893                 int       len = decimals + 1;
1895                 if (im->unitslength < len + 2)
1896                     im->unitslength = len + 2;
1897                 sprintf(im->ygrid_scale.labfmt,
1898                         "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1899             }
1900         } else {        /* classic rrd grid */
1901             for (i = 0; ylab[i].grid > 0; i++) {
1902                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1903                 gridind = i;
1904                 if (pixel >= 5)
1905                     break;
1906             }
1908             for (i = 0; i < 4; i++) {
1909                 if (pixel * ylab[gridind].lfac[i] >=
1910                     1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1911                     im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1912                     break;
1913                 }
1914             }
1916             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1917         }
1918     } else {
1919         im->ygrid_scale.gridstep = im->ygridstep;
1920         im->ygrid_scale.labfact = im->ylabfact;
1921     }
1922     return 1;
1925 int draw_horizontal_grid(
1926     image_desc_t
1927     *im)
1929     int       i;
1930     double    scaledstep;
1931     char      graph_label[100];
1932     int       nlabels = 0;
1933     double    X0 = im->xorigin;
1934     double    X1 = im->xorigin + im->xsize;
1935     int       sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1936     int       egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1937     double    MaxY;
1938     double second_axis_magfact = 0;
1939     char *second_axis_symb = "";
1940     
1941     scaledstep =
1942         im->ygrid_scale.gridstep /
1943         (double) im->magfact * (double) im->viewfactor;
1944     MaxY = scaledstep * (double) egrid;
1945     for (i = sgrid; i <= egrid; i++) {
1946         double    Y0 = ytr(im,
1947                            im->ygrid_scale.gridstep * i);
1948         double    YN = ytr(im,
1949                            im->ygrid_scale.gridstep * (i + 1));
1951         if (floor(Y0 + 0.5) >=
1952             im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1953             /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1954                with the chosen settings. Add a label if required by settings, or if
1955                there is only one label so far and the next grid line is out of bounds. */
1956             if (i % im->ygrid_scale.labfact == 0
1957                 || (nlabels == 1
1958                     && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1959                 if (im->symbol == ' ') {
1960                     if (im->extra_flags & ALTYGRID) {
1961                         sprintf(graph_label,
1962                                 im->ygrid_scale.labfmt,
1963                                 scaledstep * (double) i);
1964                     } else {
1965                         if (MaxY < 10) {
1966                             sprintf(graph_label, "%4.1f",
1967                                     scaledstep * (double) i);
1968                         } else {
1969                             sprintf(graph_label, "%4.0f",
1970                                     scaledstep * (double) i);
1971                         }
1972                     }
1973                 } else {
1974                     char      sisym = (i == 0 ? ' ' : im->symbol);
1976                     if (im->extra_flags & ALTYGRID) {
1977                         sprintf(graph_label,
1978                                 im->ygrid_scale.labfmt,
1979                                 scaledstep * (double) i, sisym);
1980                     } else {
1981                         if (MaxY < 10) {
1982                             sprintf(graph_label, "%4.1f %c",
1983                                     scaledstep * (double) i, sisym);
1984                         } else {
1985                             sprintf(graph_label, "%4.0f %c",
1986                                     scaledstep * (double) i, sisym);
1987                         }
1988                     }
1989                 }
1990                 nlabels++;
1991                 if (im->second_axis_scale != 0){
1992                         char graph_label_right[100];
1993                         double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
1994                         if (im->second_axis_format[0] == '\0'){
1995                             if (!second_axis_magfact){
1996                                 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
1997                                 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
1998                             }
1999                             sval /= second_axis_magfact;
2000  
2001                             if(MaxY < 10) { 
2002                                 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2003                             } else {
2004                                 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2005                             }
2006                         }
2007                         else {
2008                            sprintf(graph_label_right,im->second_axis_format,sval,"");
2009                         }        
2010                         gfx_text ( im,
2011                                X1+7, Y0,
2012                                im->graph_col[GRC_FONT],
2013                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2014                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2015                                graph_label_right );
2016                 }
2017  
2018                 gfx_text(im,
2019                          X0 -
2020                          im->
2021                          text_prop[TEXT_PROP_AXIS].
2022                          size, Y0,
2023                          im->graph_col[GRC_FONT],
2024                          im->
2025                          text_prop[TEXT_PROP_AXIS].
2026                          font_desc,
2027                          im->tabwidth, 0.0,
2028                          GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2029                 gfx_line(im, X0 - 2, Y0, X0, Y0,
2030                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2031                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2032                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2033                 gfx_dashed_line(im, X0 - 2, Y0,
2034                                 X1 + 2, Y0,
2035                                 MGRIDWIDTH,
2036                                 im->
2037                                 graph_col
2038                                 [GRC_MGRID],
2039                                 im->grid_dash_on, im->grid_dash_off);
2040             } else if (!(im->extra_flags & NOMINOR)) {
2041                 gfx_line(im,
2042                          X0 - 2, Y0,
2043                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2044                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2045                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2046                 gfx_dashed_line(im, X0 - 1, Y0,
2047                                 X1 + 1, Y0,
2048                                 GRIDWIDTH,
2049                                 im->
2050                                 graph_col[GRC_GRID],
2051                                 im->grid_dash_on, im->grid_dash_off);
2052             }
2053         }
2054     }
2055     return 1;
2058 /* this is frexp for base 10 */
2059 double    frexp10(
2060     double,
2061     double *);
2062 double frexp10(
2063     double x,
2064     double *e)
2066     double    mnt;
2067     int       iexp;
2069     iexp = floor(log((double)(fabs(x))) / log(10.0));
2070     mnt = x / pow(10.0, iexp);
2071     if (mnt >= 10.0) {
2072         iexp++;
2073         mnt = x / pow(10.0, iexp);
2074     }
2075     *e = iexp;
2076     return mnt;
2080 /* logaritmic horizontal grid */
2081 int horizontal_log_grid(
2082     image_desc_t
2083     *im)
2085     double    yloglab[][10] = {
2086         {
2087          1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2088          0.0, 0.0, 0.0}, {
2089                           1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2090                           0.0, 0.0, 0.0}, {
2091                                            1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2092                                            0.0, 0.0, 0.0}, {
2093                                                             1.0, 2.0, 4.0,
2094                                                             6.0, 8.0, 10.,
2095                                                             0.0,
2096                                                             0.0, 0.0, 0.0}, {
2097                                                                              1.0,
2098                                                                              2.0,
2099                                                                              3.0,
2100                                                                              4.0,
2101                                                                              5.0,
2102                                                                              6.0,
2103                                                                              7.0,
2104                                                                              8.0,
2105                                                                              9.0,
2106                                                                              10.},
2107         {
2108          0, 0, 0, 0, 0, 0, 0, 0, 0, 0}  /* last line */
2109     };
2110     int       i, j, val_exp, min_exp;
2111     double    nex;      /* number of decades in data */
2112     double    logscale; /* scale in logarithmic space */
2113     int       exfrac = 1;   /* decade spacing */
2114     int       mid = -1; /* row in yloglab for major grid */
2115     double    mspac;    /* smallest major grid spacing (pixels) */
2116     int       flab;     /* first value in yloglab to use */
2117     double    value, tmp, pre_value;
2118     double    X0, X1, Y0;
2119     char      graph_label[100];
2121     nex = log10(im->maxval / im->minval);
2122     logscale = im->ysize / nex;
2123     /* major spacing for data with high dynamic range */
2124     while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2125         if (exfrac == 1)
2126             exfrac = 3;
2127         else
2128             exfrac += 3;
2129     }
2131     /* major spacing for less dynamic data */
2132     do {
2133         /* search best row in yloglab */
2134         mid++;
2135         for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2136         mspac = logscale * log10(10.0 / yloglab[mid][i]);
2137     }
2138     while (mspac >
2139            2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2140     if (mid)
2141         mid--;
2142     /* find first value in yloglab */
2143     for (flab = 0;
2144          yloglab[mid][flab] < 10
2145          && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2146     if (yloglab[mid][flab] == 10.0) {
2147         tmp += 1.0;
2148         flab = 0;
2149     }
2150     val_exp = tmp;
2151     if (val_exp % exfrac)
2152         val_exp += abs(-val_exp % exfrac);
2153     X0 = im->xorigin;
2154     X1 = im->xorigin + im->xsize;
2155     /* draw grid */
2156     pre_value = DNAN;
2157     while (1) {
2159         value = yloglab[mid][flab] * pow(10.0, val_exp);
2160         if (AlmostEqual2sComplement(value, pre_value, 4))
2161             break;      /* it seems we are not converging */
2162         pre_value = value;
2163         Y0 = ytr(im, value);
2164         if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2165             break;
2166         /* major grid line */
2167         gfx_line(im,
2168                  X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2169         gfx_line(im, X1, Y0, X1 + 2, Y0,
2170                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2171         gfx_dashed_line(im, X0 - 2, Y0,
2172                         X1 + 2, Y0,
2173                         MGRIDWIDTH,
2174                         im->
2175                         graph_col
2176                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2177         /* label */
2178         if (im->extra_flags & FORCE_UNITS_SI) {
2179             int       scale;
2180             double    pvalue;
2181             char      symbol;
2183             scale = floor(val_exp / 3.0);
2184             if (value >= 1.0)
2185                 pvalue = pow(10.0, val_exp % 3);
2186             else
2187                 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2188             pvalue *= yloglab[mid][flab];
2189             if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2190                 && ((scale + si_symbcenter) >= 0))
2191                 symbol = si_symbol[scale + si_symbcenter];
2192             else
2193                 symbol = '?';
2194             sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2195         } else {            
2196             sprintf(graph_label, "%3.0e", value);
2197         }
2198         if (im->second_axis_scale != 0){
2199                 char graph_label_right[100];
2200                 double sval = value*im->second_axis_scale+im->second_axis_shift;
2201                 if (im->second_axis_format[0] == '\0'){
2202                         if (im->extra_flags & FORCE_UNITS_SI) {
2203                                 double mfac = 1;
2204                                 char   *symb = "";
2205                                 auto_scale(im,&sval,&symb,&mfac);
2206                                 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2207                         }
2208                         else {        
2209                                 sprintf(graph_label_right,"%3.0e", sval);
2210                         }
2211                 }
2212                 else {
2213                       sprintf(graph_label_right,im->second_axis_format,sval);
2214                 }    
2215     
2216                 gfx_text ( im,
2217                                X1+7, Y0,
2218                                im->graph_col[GRC_FONT],
2219                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2220                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2221                                graph_label_right );
2222         }
2223       
2224         gfx_text(im,
2225                  X0 -
2226                  im->
2227                  text_prop[TEXT_PROP_AXIS].
2228                  size, Y0,
2229                  im->graph_col[GRC_FONT],
2230                  im->
2231                  text_prop[TEXT_PROP_AXIS].
2232                  font_desc,
2233                  im->tabwidth, 0.0,
2234                  GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2235         /* minor grid */
2236         if (mid < 4 && exfrac == 1) {
2237             /* find first and last minor line behind current major line
2238              * i is the first line and j tha last */
2239             if (flab == 0) {
2240                 min_exp = val_exp - 1;
2241                 for (i = 1; yloglab[mid][i] < 10.0; i++);
2242                 i = yloglab[mid][i - 1] + 1;
2243                 j = 10;
2244             } else {
2245                 min_exp = val_exp;
2246                 i = yloglab[mid][flab - 1] + 1;
2247                 j = yloglab[mid][flab];
2248             }
2250             /* draw minor lines below current major line */
2251             for (; i < j; i++) {
2253                 value = i * pow(10.0, min_exp);
2254                 if (value < im->minval)
2255                     continue;
2256                 Y0 = ytr(im, value);
2257                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2258                     break;
2259                 /* draw lines */
2260                 gfx_line(im,
2261                          X0 - 2, Y0,
2262                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2263                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2264                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2265                 gfx_dashed_line(im, X0 - 1, Y0,
2266                                 X1 + 1, Y0,
2267                                 GRIDWIDTH,
2268                                 im->
2269                                 graph_col[GRC_GRID],
2270                                 im->grid_dash_on, im->grid_dash_off);
2271             }
2272         } else if (exfrac > 1) {
2273             for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2274                 value = pow(10.0, i);
2275                 if (value < im->minval)
2276                     continue;
2277                 Y0 = ytr(im, value);
2278                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2279                     break;
2280                 /* draw lines */
2281                 gfx_line(im,
2282                          X0 - 2, Y0,
2283                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2284                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2285                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2286                 gfx_dashed_line(im, X0 - 1, Y0,
2287                                 X1 + 1, Y0,
2288                                 GRIDWIDTH,
2289                                 im->
2290                                 graph_col[GRC_GRID],
2291                                 im->grid_dash_on, im->grid_dash_off);
2292             }
2293         }
2295         /* next decade */
2296         if (yloglab[mid][++flab] == 10.0) {
2297             flab = 0;
2298             val_exp += exfrac;
2299         }
2300     }
2302     /* draw minor lines after highest major line */
2303     if (mid < 4 && exfrac == 1) {
2304         /* find first and last minor line below current major line
2305          * i is the first line and j tha last */
2306         if (flab == 0) {
2307             min_exp = val_exp - 1;
2308             for (i = 1; yloglab[mid][i] < 10.0; i++);
2309             i = yloglab[mid][i - 1] + 1;
2310             j = 10;
2311         } else {
2312             min_exp = val_exp;
2313             i = yloglab[mid][flab - 1] + 1;
2314             j = yloglab[mid][flab];
2315         }
2317         /* draw minor lines below current major line */
2318         for (; i < j; i++) {
2320             value = i * pow(10.0, min_exp);
2321             if (value < im->minval)
2322                 continue;
2323             Y0 = ytr(im, value);
2324             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2325                 break;
2326             /* draw lines */
2327             gfx_line(im,
2328                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2329             gfx_line(im, X1, Y0, X1 + 2, Y0,
2330                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2331             gfx_dashed_line(im, X0 - 1, Y0,
2332                             X1 + 1, Y0,
2333                             GRIDWIDTH,
2334                             im->
2335                             graph_col[GRC_GRID],
2336                             im->grid_dash_on, im->grid_dash_off);
2337         }
2338     }
2339     /* fancy minor gridlines */
2340     else if (exfrac > 1) {
2341         for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2342             value = pow(10.0, i);
2343             if (value < im->minval)
2344                 continue;
2345             Y0 = ytr(im, value);
2346             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2347                 break;
2348             /* draw lines */
2349             gfx_line(im,
2350                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2351             gfx_line(im, X1, Y0, X1 + 2, Y0,
2352                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2353             gfx_dashed_line(im, X0 - 1, Y0,
2354                             X1 + 1, Y0,
2355                             GRIDWIDTH,
2356                             im->
2357                             graph_col[GRC_GRID],
2358                             im->grid_dash_on, im->grid_dash_off);
2359         }
2360     }
2362     return 1;
2366 void vertical_grid(
2367     image_desc_t *im)
2369     int       xlab_sel; /* which sort of label and grid ? */
2370     time_t    ti, tilab, timajor;
2371     long      factor;
2372     char      graph_label[100];
2373     double    X0, Y0, Y1;   /* points for filled graph and more */
2374     struct tm tm;
2376     /* the type of time grid is determined by finding
2377        the number of seconds per pixel in the graph */
2378     if (im->xlab_user.minsec == -1) {
2379         factor = (im->end - im->start) / im->xsize;
2380         xlab_sel = 0;
2381         while (xlab[xlab_sel + 1].minsec !=
2382                -1 && xlab[xlab_sel + 1].minsec <= factor) {
2383             xlab_sel++;
2384         }               /* pick the last one */
2385         while (xlab[xlab_sel - 1].minsec ==
2386                xlab[xlab_sel].minsec
2387                && xlab[xlab_sel].length > (im->end - im->start)) {
2388             xlab_sel--;
2389         }               /* go back to the smallest size */
2390         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2391         im->xlab_user.gridst = xlab[xlab_sel].gridst;
2392         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2393         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2394         im->xlab_user.labtm = xlab[xlab_sel].labtm;
2395         im->xlab_user.labst = xlab[xlab_sel].labst;
2396         im->xlab_user.precis = xlab[xlab_sel].precis;
2397         im->xlab_user.stst = xlab[xlab_sel].stst;
2398     }
2400     /* y coords are the same for every line ... */
2401     Y0 = im->yorigin;
2402     Y1 = im->yorigin - im->ysize;
2403     /* paint the minor grid */
2404     if (!(im->extra_flags & NOMINOR)) {
2405         for (ti = find_first_time(im->start,
2406                                   im->
2407                                   xlab_user.
2408                                   gridtm,
2409                                   im->
2410                                   xlab_user.
2411                                   gridst),
2412              timajor =
2413              find_first_time(im->start,
2414                              im->xlab_user.
2415                              mgridtm,
2416                              im->xlab_user.
2417                              mgridst);
2418              ti < im->end;
2419              ti =
2420              find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2421             ) {
2422             /* are we inside the graph ? */
2423             if (ti < im->start || ti > im->end)
2424                 continue;
2425             while (timajor < ti) {
2426                 timajor = find_next_time(timajor,
2427                                          im->
2428                                          xlab_user.
2429                                          mgridtm, im->xlab_user.mgridst);
2430             }
2431             if (ti == timajor)
2432                 continue;   /* skip as falls on major grid line */
2433             X0 = xtr(im, ti);
2434             gfx_line(im, X0, Y1 - 2, X0, Y1,
2435                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2436             gfx_line(im, X0, Y0, X0, Y0 + 2,
2437                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2438             gfx_dashed_line(im, X0, Y0 + 1, X0,
2439                             Y1 - 1, GRIDWIDTH,
2440                             im->
2441                             graph_col[GRC_GRID],
2442                             im->grid_dash_on, im->grid_dash_off);
2443         }
2444     }
2446     /* paint the major grid */
2447     for (ti = find_first_time(im->start,
2448                               im->
2449                               xlab_user.
2450                               mgridtm,
2451                               im->
2452                               xlab_user.
2453                               mgridst);
2454          ti < im->end;
2455          ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2456         ) {
2457         /* are we inside the graph ? */
2458         if (ti < im->start || ti > im->end)
2459             continue;
2460         X0 = xtr(im, ti);
2461         gfx_line(im, X0, Y1 - 2, X0, Y1,
2462                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2463         gfx_line(im, X0, Y0, X0, Y0 + 3,
2464                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2465         gfx_dashed_line(im, X0, Y0 + 3, X0,
2466                         Y1 - 2, MGRIDWIDTH,
2467                         im->
2468                         graph_col
2469                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2470     }
2471     /* paint the labels below the graph */
2472     for (ti =
2473          find_first_time(im->start -
2474                          im->xlab_user.
2475                          precis / 2,
2476                          im->xlab_user.
2477                          labtm,
2478                          im->xlab_user.
2479                          labst);
2480          ti <=
2481          im->end -
2482          im->xlab_user.precis / 2;
2483          ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2484         ) {
2485         tilab = ti + im->xlab_user.precis / 2;  /* correct time for the label */
2486         /* are we inside the graph ? */
2487         if (tilab < im->start || tilab > im->end)
2488             continue;
2489 #if HAVE_STRFTIME
2490         localtime_r(&tilab, &tm);
2491         strftime(graph_label, 99, im->xlab_user.stst, &tm);
2492 #else
2493 # error "your libc has no strftime I guess we'll abort the exercise here."
2494 #endif
2495         gfx_text(im,
2496                  xtr(im, tilab),
2497                  Y0 + 3,
2498                  im->graph_col[GRC_FONT],
2499                  im->
2500                  text_prop[TEXT_PROP_AXIS].
2501                  font_desc,
2502                  im->tabwidth, 0.0,
2503                  GFX_H_CENTER, GFX_V_TOP, graph_label);
2504     }
2509 void axis_paint(
2510     image_desc_t *im)
2512     /* draw x and y axis */
2513     /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2514        im->xorigin+im->xsize,im->yorigin-im->ysize,
2515        GRIDWIDTH, im->graph_col[GRC_AXIS]);
2517        gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2518        im->xorigin+im->xsize,im->yorigin-im->ysize,
2519        GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2521     gfx_line(im, im->xorigin - 4,
2522              im->yorigin,
2523              im->xorigin + im->xsize +
2524              4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2525     gfx_line(im, im->xorigin,
2526              im->yorigin + 4,
2527              im->xorigin,
2528              im->yorigin - im->ysize -
2529              4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2530     /* arrow for X and Y axis direction */
2531     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 */
2532                  im->graph_col[GRC_ARROW]);
2533     gfx_close_path(im);
2534     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 */
2535                  im->graph_col[GRC_ARROW]);
2536     gfx_close_path(im);
2537     if (im->second_axis_scale != 0){
2538        gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2539                          im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2540                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2541        gfx_new_area ( im, 
2542                    im->xorigin+im->xsize-2,  im->yorigin-im->ysize-2,
2543                    im->xorigin+im->xsize+3,  im->yorigin-im->ysize-2,
2544                    im->xorigin+im->xsize,    im->yorigin-im->ysize-7, /* LINEOFFSET */
2545                    im->graph_col[GRC_ARROW]);
2546        gfx_close_path(im);
2547     }
2551 void grid_paint(
2552     image_desc_t *im)
2554     long      i;
2555     int       res = 0;
2556     double    X0, Y0;   /* points for filled graph and more */
2557     struct gfx_color_t water_color;
2559     /* draw 3d border */
2560     gfx_new_area(im, 0, im->yimg,
2561                  2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2562     gfx_add_point(im, im->ximg - 2, 2);
2563     gfx_add_point(im, im->ximg, 0);
2564     gfx_add_point(im, 0, 0);
2565     gfx_close_path(im);
2566     gfx_new_area(im, 2, im->yimg - 2,
2567                  im->ximg - 2,
2568                  im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2569     gfx_add_point(im, im->ximg, 0);
2570     gfx_add_point(im, im->ximg, im->yimg);
2571     gfx_add_point(im, 0, im->yimg);
2572     gfx_close_path(im);
2573     if (im->draw_x_grid == 1)
2574         vertical_grid(im);
2575     if (im->draw_y_grid == 1) {
2576         if (im->logarithmic) {
2577             res = horizontal_log_grid(im);
2578         } else {
2579             res = draw_horizontal_grid(im);
2580         }
2582         /* dont draw horizontal grid if there is no min and max val */
2583         if (!res) {
2584             char     *nodata = "No Data found";
2586             gfx_text(im, im->ximg / 2,
2587                      (2 * im->yorigin -
2588                       im->ysize) / 2,
2589                      im->graph_col[GRC_FONT],
2590                      im->
2591                      text_prop[TEXT_PROP_AXIS].
2592                      font_desc,
2593                      im->tabwidth, 0.0,
2594                      GFX_H_CENTER, GFX_V_CENTER, nodata);
2595         }
2596     }
2598     /* yaxis unit description */
2599     if (im->ylegend[0] != '\0'){
2600         gfx_text(im,
2601                  10,
2602                  (im->yorigin -
2603                   im->ysize / 2),
2604                  im->graph_col[GRC_FONT],
2605                  im->
2606                  text_prop[TEXT_PROP_UNIT].
2607                  font_desc,
2608                  im->tabwidth,
2609                  RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2610     }
2611     if (im->second_axis_legend[0] != '\0'){
2612             double Xylabel=gfx_get_text_width(im, 0,
2613                         im->text_prop[TEXT_PROP_AXIS].font_desc,
2614                         im->tabwidth,
2615                         "0") * im->unitslength
2616                     + im->text_prop[TEXT_PROP_UNIT].size *2;
2617             gfx_text( im,
2618                   im->xorigin+im->xsize+Xylabel+8, (im->yorigin - im->ysize/2),
2619                   im->graph_col[GRC_FONT],
2620                   im->text_prop[TEXT_PROP_UNIT].font_desc,
2621                   im->tabwidth, 
2622                   RRDGRAPH_YLEGEND_ANGLE,
2623                   GFX_H_CENTER, GFX_V_CENTER,
2624                   im->second_axis_legend);
2625     }        
2626  
2627     /* graph title */
2628     gfx_text(im,
2629              im->ximg / 2, 6,
2630              im->graph_col[GRC_FONT],
2631              im->
2632              text_prop[TEXT_PROP_TITLE].
2633              font_desc,
2634              im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2635     /* rrdtool 'logo' */
2636     if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2637         water_color = im->graph_col[GRC_FONT];
2638         water_color.alpha = 0.3;
2639         gfx_text(im, im->ximg - 4, 5,
2640                  water_color,
2641                  im->
2642                  text_prop[TEXT_PROP_WATERMARK].
2643                  font_desc, im->tabwidth,
2644                  -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2645     }    
2646     /* graph watermark */
2647     if (im->watermark[0] != '\0') {
2648         gfx_text(im,
2649                  im->ximg / 2, im->yimg - 6,
2650                  water_color,
2651                  im->
2652                  text_prop[TEXT_PROP_WATERMARK].
2653                  font_desc, im->tabwidth, 0,
2654                  GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2655     }
2657     /* graph labels */
2658     if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2659         for (i = 0; i < im->gdes_c; i++) {
2660             if (im->gdes[i].legend[0] == '\0')
2661                 continue;
2662             /* im->gdes[i].leg_y is the bottom of the legend */
2663             X0 = im->gdes[i].leg_x;
2664             Y0 = im->gdes[i].leg_y;
2665             gfx_text(im, X0, Y0,
2666                      im->graph_col[GRC_FONT],
2667                      im->
2668                      text_prop
2669                      [TEXT_PROP_LEGEND].font_desc,
2670                      im->tabwidth, 0.0,
2671                      GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2672             /* The legend for GRAPH items starts with "M " to have
2673                enough space for the box */
2674             if (im->gdes[i].gf != GF_PRINT &&
2675                 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2676                 double    boxH, boxV;
2677                 double    X1, Y1;
2679                 boxH = gfx_get_text_width(im, 0,
2680                                           im->
2681                                           text_prop
2682                                           [TEXT_PROP_LEGEND].
2683                                           font_desc,
2684                                           im->tabwidth, "o") * 1.2;
2685                 boxV = boxH;
2686                 /* shift the box up a bit */
2687                 Y0 -= boxV * 0.4;
2688                 /* make sure transparent colors show up the same way as in the graph */
2689                 gfx_new_area(im,
2690                              X0, Y0 - boxV,
2691                              X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2692                 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2693                 gfx_close_path(im);
2694                 gfx_new_area(im, X0, Y0 - boxV, X0,
2695                              Y0, X0 + boxH, Y0, im->gdes[i].col);
2696                 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2697                 gfx_close_path(im);
2698                 cairo_save(im->cr);
2699                 cairo_new_path(im->cr);
2700                 cairo_set_line_width(im->cr, 1.0);
2701                 X1 = X0 + boxH;
2702                 Y1 = Y0 - boxV;
2703                 gfx_line_fit(im, &X0, &Y0);
2704                 gfx_line_fit(im, &X1, &Y1);
2705                 cairo_move_to(im->cr, X0, Y0);
2706                 cairo_line_to(im->cr, X1, Y0);
2707                 cairo_line_to(im->cr, X1, Y1);
2708                 cairo_line_to(im->cr, X0, Y1);
2709                 cairo_close_path(im->cr);
2710                 cairo_set_source_rgba(im->cr,
2711                                       im->
2712                                       graph_col
2713                                       [GRC_FRAME].
2714                                       red,
2715                                       im->
2716                                       graph_col
2717                                       [GRC_FRAME].
2718                                       green,
2719                                       im->
2720                                       graph_col
2721                                       [GRC_FRAME].
2722                                       blue, im->graph_col[GRC_FRAME].alpha);
2723                 if (im->gdes[i].dash) {
2724                     /* make box borders in legend dashed if the graph is dashed */
2725                     double    dashes[] = {
2726                         3.0
2727                     };
2728                     cairo_set_dash(im->cr, dashes, 1, 0.0);
2729                 }
2730                 cairo_stroke(im->cr);
2731                 cairo_restore(im->cr);
2732             }
2733         }
2734     }
2738 /*****************************************************
2739  * lazy check make sure we rely need to create this graph
2740  *****************************************************/
2742 int lazy_check(
2743     image_desc_t *im)
2745     FILE     *fd = NULL;
2746     int       size = 1;
2747     struct stat imgstat;
2749     if (im->lazy == 0)
2750         return 0;       /* no lazy option */
2751     if (strlen(im->graphfile) == 0)
2752         return 0;       /* inmemory option */
2753     if (stat(im->graphfile, &imgstat) != 0)
2754         return 0;       /* can't stat */
2755     /* one pixel in the existing graph is more then what we would
2756        change here ... */
2757     if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2758         return 0;
2759     if ((fd = fopen(im->graphfile, "rb")) == NULL)
2760         return 0;       /* the file does not exist */
2761     switch (im->imgformat) {
2762     case IF_PNG:
2763         size = PngSize(fd, &(im->ximg), &(im->yimg));
2764         break;
2765     default:
2766         size = 1;
2767     }
2768     fclose(fd);
2769     return size;
2773 int graph_size_location(
2774     image_desc_t
2775     *im,
2776     int elements)
2778     /* The actual size of the image to draw is determined from
2779      ** several sources.  The size given on the command line is
2780      ** the graph area but we need more as we have to draw labels
2781      ** and other things outside the graph area
2782      */
2784     int       Xvertical = 0, Ytitle =
2785         0, Xylabel = 0, Xmain = 0, Ymain =
2786         0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2788     if (im->extra_flags & ONLY_GRAPH) {
2789         im->xorigin = 0;
2790         im->ximg = im->xsize;
2791         im->yimg = im->ysize;
2792         im->yorigin = im->ysize;
2793         ytr(im, DNAN);
2794         return 0;
2795     }
2797     /** +---+-----------------------------------+
2798      ** | y |...............graph title.........|
2799      ** |   +---+-------------------------------+
2800      ** | a | y |                               |
2801      ** | x |   |                               |
2802      ** | i | a |                               |    
2803      ** | s | x |       main graph area         |
2804      ** |   | i |                               |
2805      ** | t | s |                               |
2806      ** | i |   |                               |
2807      ** | t | l |                               |
2808      ** | l | b +-------------------------------+
2809      ** | e | l |       x axis labels           |
2810      ** +---+---+-------------------------------+
2811      ** |....................legends............|
2812      ** +---------------------------------------+
2813      ** |                   watermark           |
2814      ** +---------------------------------------+
2815      */
2817     if (im->ylegend[0] != '\0') {
2818         Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2819     }
2821     if (im->title[0] != '\0') {
2822         /* The title is placed "inbetween" two text lines so it
2823          ** automatically has some vertical spacing.  The horizontal
2824          ** spacing is added here, on each side.
2825          */
2826         /* if necessary, reduce the font size of the title until it fits the image width */
2827         Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2828     }
2830     if (elements) {
2831         if (im->draw_x_grid) {
2832             Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2833         }
2834         if (im->draw_y_grid || im->forceleftspace) {
2835             Xylabel =
2836                 gfx_get_text_width(im, 0,
2837                                    im->
2838                                    text_prop
2839                                    [TEXT_PROP_AXIS].
2840                                    font_desc,
2841                                    im->tabwidth, "0") * im->unitslength;
2842         }
2843     }
2845     if (im->extra_flags & FULL_SIZE_MODE) {
2846         /* The actual size of the image to draw has been determined by the user.
2847          ** The graph area is the space remaining after accounting for the legend,
2848          ** the watermark, the axis labels, and the title.
2849          */
2850         im->xorigin = 0;
2851         im->ximg = im->xsize;
2852         im->yimg = im->ysize;
2853         im->yorigin = im->ysize;
2854         Xmain = im->ximg;
2855         Ymain = im->yimg;
2856         /* Now calculate the total size.  Insert some spacing where
2857            desired.  im->xorigin and im->yorigin need to correspond
2858            with the lower left corner of the main graph area or, if
2859            this one is not set, the imaginary box surrounding the
2860            pie chart area. */
2861         /* Initial size calculation for the main graph area */
2862         Xmain = im->ximg - Xylabel - 3 * Xspacing;
2864         im->xorigin = Xspacing + Xylabel;
2866         if (Xvertical) {    /* unit description */
2867             Xmain -= Xvertical;
2868             im->xorigin += Xvertical;
2869         }
2871         /* adjust space for second axis */
2872         if (im->second_axis_scale != 0){
2873             Xmain -= Xylabel + Xspacing;
2874         }
2875         if (im->extra_flags & NO_RRDTOOL_TAG){
2876             Xmain += Xspacing;
2877         }
2878         if (im->second_axis_legend[0] != '\0' ) {
2879             Xmain -= im->text_prop[TEXT_PROP_UNIT].size * 1.5;
2880         }
2882         im->xsize = Xmain;
2884         xtr(im, 0);
2885         /* The vertical size of the image is known in advance.  The main graph area
2886          ** (Ymain) and im->yorigin must be set according to the space requirements
2887          ** of the legend and the axis labels.
2888          */
2889         if (im->extra_flags & NOLEGEND) {
2890             im->yorigin = im->yimg -
2891                 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2892             Ymain = im->yorigin;
2893         }
2894         else {            
2895             /* Determine where to place the legends onto the image.
2896              ** Set Ymain and adjust im->yorigin to match the space requirements.
2897              */
2898             if (leg_place(im, &Ymain) == -1)
2899                 return -1;
2900         }
2903         /* remove title space *or* some padding above the graph from the main graph area */
2904         if (Ytitle) {
2905             Ymain -= Ytitle;
2906         } else {
2907             Ymain -= 1.5 * Yspacing;
2908         }
2910         /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2911         if (im->watermark[0] != '\0') {
2912             Ymain -= Ywatermark;
2913         }
2915         im->ysize = Ymain;
2916     } else {            /* dimension options -width and -height refer to the dimensions of the main graph area */
2918         /* The actual size of the image to draw is determined from
2919          ** several sources.  The size given on the command line is
2920          ** the graph area but we need more as we have to draw labels
2921          ** and other things outside the graph area.
2922          */
2924         if (im->ylegend[0] != '\0') {
2925             Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2926         }
2929         if (im->title[0] != '\0') {
2930             /* The title is placed "inbetween" two text lines so it
2931              ** automatically has some vertical spacing.  The horizontal
2932              ** spacing is added here, on each side.
2933              */
2934             /* don't care for the with of the title
2935                Xtitle = gfx_get_text_width(im->canvas, 0,
2936                im->text_prop[TEXT_PROP_TITLE].font_desc,
2937                im->tabwidth,
2938                im->title, 0) + 2*Xspacing; */
2939             Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2940         }
2942         if (elements) {
2943             Xmain = im->xsize;
2944             Ymain = im->ysize;
2945         }
2946         /* Now calculate the total size.  Insert some spacing where
2947            desired.  im->xorigin and im->yorigin need to correspond
2948            with the lower left corner of the main graph area or, if
2949            this one is not set, the imaginary box surrounding the
2950            pie chart area. */
2952         /* The legend width cannot yet be determined, as a result we
2953          ** have problems adjusting the image to it.  For now, we just
2954          ** forget about it at all; the legend will have to fit in the
2955          ** size already allocated.
2956          */
2957         im->ximg = Xylabel + Xmain + 2 * Xspacing;
2959         if (im->second_axis_scale != 0){
2960             im->ximg += Xylabel + Xspacing;
2961         }
2962         if (im->extra_flags & NO_RRDTOOL_TAG){
2963             im->ximg -= Xspacing;
2964         }
2965         
2966         if (Xmain)
2967             im->ximg += Xspacing;
2968         im->xorigin = Xspacing + Xylabel;
2969         /* the length of the title should not influence with width of the graph
2970            if (Xtitle > im->ximg) im->ximg = Xtitle; */
2971         if (Xvertical) {    /* unit description */
2972             im->ximg += Xvertical;
2973             im->xorigin += Xvertical;
2974         }
2975         if (im->second_axis_legend[0] != '\0' ) {
2976             im->ximg += Xvertical;
2977         }
2978       
2979         xtr(im, 0);
2980         /* The vertical size is interesting... we need to compare
2981          ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with 
2982          ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2983          ** in order to start even thinking about Ylegend or Ywatermark.
2984          **
2985          ** Do it in three portions: First calculate the inner part,
2986          ** then do the legend, then adjust the total height of the img,
2987          ** adding space for a watermark if one exists;
2988          */
2989         /* reserve space for main and/or pie */
2990         im->yimg = Ymain + Yxlabel;
2991         im->yorigin = im->yimg - Yxlabel;
2992         /* reserve space for the title *or* some padding above the graph */
2993         if (Ytitle) {
2994             im->yimg += Ytitle;
2995             im->yorigin += Ytitle;
2996         } else {
2997             im->yimg += 1.5 * Yspacing;
2998             im->yorigin += 1.5 * Yspacing;
2999         }
3000         /* reserve space for padding below the graph */
3001         im->yimg += Yspacing;
3002         /* Determine where to place the legends onto the image.
3003          ** Adjust im->yimg to match the space requirements.
3004          */
3005         if (leg_place(im, 0) == -1)
3006             return -1;
3007         if (im->watermark[0] != '\0') {
3008             im->yimg += Ywatermark;
3009         }
3010     }
3012     ytr(im, DNAN);
3013     return 0;
3016 static cairo_status_t cairo_output(
3017     void *closure,
3018     const unsigned char
3019     *data,
3020     unsigned int length)
3022     image_desc_t *im = (image_desc_t*)(closure);
3024     im->rendered_image =
3025         (unsigned char*)(realloc(im->rendered_image, im->rendered_image_size + length));
3026     if (im->rendered_image == NULL)
3027         return CAIRO_STATUS_WRITE_ERROR;
3028     memcpy(im->rendered_image + im->rendered_image_size, data, length);
3029     im->rendered_image_size += length;
3030     return CAIRO_STATUS_SUCCESS;
3033 /* draw that picture thing ... */
3034 int graph_paint(
3035     image_desc_t *im)
3037     int       i, ii;
3038     int       lazy = lazy_check(im);
3039     double    areazero = 0.0;
3040     graph_desc_t *lastgdes = NULL;
3041     rrd_infoval_t info;
3044     /* pull the data from the rrd files ... */
3045     if (data_fetch(im) == -1)
3046         return -1;
3047     /* evaluate VDEF and CDEF operations ... */
3048     if (data_calc(im) == -1)
3049         return -1;
3050     /* calculate and PRINT and GPRINT definitions. We have to do it at
3051      * this point because it will affect the length of the legends
3052      * if there are no graph elements (i==0) we stop here ... 
3053      * if we are lazy, try to quit ... 
3054      */
3055     i = print_calc(im);
3056     if (i < 0)
3057         return -1;
3059     if (i == 0)
3060         return 0;
3062 /**************************************************************
3063  *** Calculating sizes and locations became a bit confusing ***
3064  *** so I moved this into a separate function.              ***
3065  **************************************************************/
3066     if (graph_size_location(im, i) == -1)
3067         return -1;
3069     info.u_cnt = im->xorigin;
3070     grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3071     info.u_cnt = im->yorigin - im->ysize;
3072     grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3073     info.u_cnt = im->xsize;
3074     grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3075     info.u_cnt = im->ysize;
3076     grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3077     info.u_cnt = im->ximg;
3078     grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3079     info.u_cnt = im->yimg;
3080     grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3081     info.u_cnt = im->start;
3082     grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3083     info.u_cnt = im->end;
3084     grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3086     /* if we want and can be lazy ... quit now */
3087     if (lazy)
3088         return 0;
3090     /* get actual drawing data and find min and max values */
3091     if (data_proc(im) == -1)
3092         return -1;
3093     if (!im->logarithmic) {
3094         si_unit(im);
3095     }
3097     /* identify si magnitude Kilo, Mega Giga ? */
3098     if (!im->rigid && !im->logarithmic)
3099         expand_range(im);   /* make sure the upper and lower limit are
3100                                sensible values */
3102     info.u_val = im->minval;
3103     grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3104     info.u_val = im->maxval;
3105     grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3107     if (!calc_horizontal_grid(im))
3108         return -1;
3109     /* reset precalc */
3110     ytr(im, DNAN);
3111 /*   if (im->gridfit)
3112      apply_gridfit(im); */
3113     /* the actual graph is created by going through the individual
3114        graph elements and then drawing them */
3115     cairo_surface_destroy(im->surface);
3116     switch (im->imgformat) {
3117     case IF_PNG:
3118         im->surface =
3119             cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3120                                        im->ximg * im->zoom,
3121                                        im->yimg * im->zoom);
3122         break;
3123     case IF_PDF:
3124         im->gridfit = 0;
3125         im->surface = strlen(im->graphfile)
3126             ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3127                                        im->yimg * im->zoom)
3128             : cairo_pdf_surface_create_for_stream
3129             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3130         break;
3131     case IF_EPS:
3132         im->gridfit = 0;
3133         im->surface = strlen(im->graphfile)
3134             ?
3135             cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3136                                     im->yimg * im->zoom)
3137             : cairo_ps_surface_create_for_stream
3138             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3139         break;
3140     case IF_SVG:
3141         im->gridfit = 0;
3142         im->surface = strlen(im->graphfile)
3143             ?
3144             cairo_svg_surface_create(im->
3145                                      graphfile,
3146                                      im->ximg * im->zoom, im->yimg * im->zoom)
3147             : cairo_svg_surface_create_for_stream
3148             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3149         cairo_svg_surface_restrict_to_version
3150             (im->surface, CAIRO_SVG_VERSION_1_1);
3151         break;
3152     };
3153     cairo_destroy(im->cr);
3154     im->cr = cairo_create(im->surface);
3155     cairo_set_antialias(im->cr, im->graph_antialias);
3156     cairo_scale(im->cr, im->zoom, im->zoom);
3157 //    pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3158     gfx_new_area(im, 0, 0, 0, im->yimg,
3159                  im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3160     gfx_add_point(im, im->ximg, 0);
3161     gfx_close_path(im);
3162     gfx_new_area(im, im->xorigin,
3163                  im->yorigin,
3164                  im->xorigin +
3165                  im->xsize, im->yorigin,
3166                  im->xorigin +
3167                  im->xsize,
3168                  im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3169     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3170     gfx_close_path(im);
3171     cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3172                     im->xsize, im->ysize + 2.0);
3173     cairo_clip(im->cr);
3174     if (im->minval > 0.0)
3175         areazero = im->minval;
3176     if (im->maxval < 0.0)
3177         areazero = im->maxval;
3178     for (i = 0; i < im->gdes_c; i++) {
3179         switch (im->gdes[i].gf) {
3180         case GF_CDEF:
3181         case GF_VDEF:
3182         case GF_DEF:
3183         case GF_PRINT:
3184         case GF_GPRINT:
3185         case GF_COMMENT:
3186         case GF_TEXTALIGN:
3187         case GF_HRULE:
3188         case GF_VRULE:
3189         case GF_XPORT:
3190         case GF_SHIFT:
3191             break;
3192         case GF_TICK:
3193             for (ii = 0; ii < im->xsize; ii++) {
3194                 if (!isnan(im->gdes[i].p_data[ii])
3195                     && im->gdes[i].p_data[ii] != 0.0) {
3196                     if (im->gdes[i].yrule > 0) {
3197                         gfx_line(im,
3198                                  im->xorigin + ii,
3199                                  im->yorigin + 1.0,
3200                                  im->xorigin + ii,
3201                                  im->yorigin -
3202                                  im->gdes[i].yrule *
3203                                  im->ysize, 1.0, im->gdes[i].col);
3204                     } else if (im->gdes[i].yrule < 0) {
3205                         gfx_line(im,
3206                                  im->xorigin + ii,
3207                                  im->yorigin - im->ysize - 1.0,
3208                                  im->xorigin + ii,
3209                                  im->yorigin - im->ysize -
3210                                                 im->gdes[i].
3211                                                 yrule *
3212                                  im->ysize, 1.0, im->gdes[i].col);
3213                     }
3214                 }
3215             }
3216             break;
3217         case GF_LINE:
3218         case GF_AREA:
3219             /* fix data points at oo and -oo */
3220             for (ii = 0; ii < im->xsize; ii++) {
3221                 if (isinf(im->gdes[i].p_data[ii])) {
3222                     if (im->gdes[i].p_data[ii] > 0) {
3223                         im->gdes[i].p_data[ii] = im->maxval;
3224                     } else {
3225                         im->gdes[i].p_data[ii] = im->minval;
3226                     }
3228                 }
3229             }           /* for */
3231             /* *******************************************************
3232                a           ___. (a,t) 
3233                |   |    ___
3234                ____|   |   |   |
3235                |       |___|
3236                -------|--t-1--t--------------------------------      
3238                if we know the value at time t was a then 
3239                we draw a square from t-1 to t with the value a.
3241                ********************************************************* */
3242             if (im->gdes[i].col.alpha != 0.0) {
3243                 /* GF_LINE and friend */
3244                 if (im->gdes[i].gf == GF_LINE) {
3245                     double    last_y = 0.0;
3246                     int       draw_on = 0;
3248                     cairo_save(im->cr);
3249                     cairo_new_path(im->cr);
3250                     cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3251                     if (im->gdes[i].dash) {
3252                         cairo_set_dash(im->cr,
3253                                        im->gdes[i].p_dashes,
3254                                        im->gdes[i].ndash, im->gdes[i].offset);
3255                     }
3257                     for (ii = 1; ii < im->xsize; ii++) {
3258                         if (isnan(im->gdes[i].p_data[ii])
3259                             || (im->slopemode == 1
3260                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3261                             draw_on = 0;
3262                             continue;
3263                         }
3264                         if (draw_on == 0) {
3265                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3266                             if (im->slopemode == 0) {
3267                                 double    x = ii - 1 + im->xorigin;
3268                                 double    y = last_y;
3270                                 gfx_line_fit(im, &x, &y);
3271                                 cairo_move_to(im->cr, x, y);
3272                                 x = ii + im->xorigin;
3273                                 y = last_y;
3274                                 gfx_line_fit(im, &x, &y);
3275                                 cairo_line_to(im->cr, x, y);
3276                             } else {
3277                                 double    x = ii - 1 + im->xorigin;
3278                                 double    y =
3279                                     ytr(im, im->gdes[i].p_data[ii - 1]);
3280                                 gfx_line_fit(im, &x, &y);
3281                                 cairo_move_to(im->cr, x, y);
3282                                 x = ii + im->xorigin;
3283                                 y = last_y;
3284                                 gfx_line_fit(im, &x, &y);
3285                                 cairo_line_to(im->cr, x, y);
3286                             }
3287                             draw_on = 1;
3288                         } else {
3289                             double    x1 = ii + im->xorigin;
3290                             double    y1 = ytr(im, im->gdes[i].p_data[ii]);
3292                             if (im->slopemode == 0
3293                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3294                                 double    x = ii - 1 + im->xorigin;
3295                                 double    y = y1;
3297                                 gfx_line_fit(im, &x, &y);
3298                                 cairo_line_to(im->cr, x, y);
3299                             };
3300                             last_y = y1;
3301                             gfx_line_fit(im, &x1, &y1);
3302                             cairo_line_to(im->cr, x1, y1);
3303                         };
3304                     }
3305                     cairo_set_source_rgba(im->cr,
3306                                           im->gdes[i].
3307                                           col.red,
3308                                           im->gdes[i].
3309                                           col.green,
3310                                           im->gdes[i].
3311                                           col.blue, im->gdes[i].col.alpha);
3312                     cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3313                     cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3314                     cairo_stroke(im->cr);
3315                     cairo_restore(im->cr);
3316                 } else {
3317                     int       idxI = -1;
3318                     double   *foreY =
3319                         (double *) malloc(sizeof(double) * im->xsize * 2);
3320                     double   *foreX =
3321                         (double *) malloc(sizeof(double) * im->xsize * 2);
3322                     double   *backY =
3323                         (double *) malloc(sizeof(double) * im->xsize * 2);
3324                     double   *backX =
3325                         (double *) malloc(sizeof(double) * im->xsize * 2);
3326                     int       drawem = 0;
3328                     for (ii = 0; ii <= im->xsize; ii++) {
3329                         double    ybase, ytop;
3331                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3332                             int       cntI = 1;
3333                             int       lastI = 0;
3335                             while (cntI < idxI
3336                                    &&
3337                                    AlmostEqual2sComplement(foreY
3338                                                            [lastI],
3339                                                            foreY[cntI], 4)
3340                                    &&
3341                                    AlmostEqual2sComplement(foreY
3342                                                            [lastI],
3343                                                            foreY
3344                                                            [cntI + 1], 4)) {
3345                                 cntI++;
3346                             }
3347                             gfx_new_area(im,
3348                                          backX[0], backY[0],
3349                                          foreX[0], foreY[0],
3350                                          foreX[cntI],
3351                                          foreY[cntI], im->gdes[i].col);
3352                             while (cntI < idxI) {
3353                                 lastI = cntI;
3354                                 cntI++;
3355                                 while (cntI < idxI
3356                                        &&
3357                                        AlmostEqual2sComplement(foreY
3358                                                                [lastI],
3359                                                                foreY[cntI], 4)
3360                                        &&
3361                                        AlmostEqual2sComplement(foreY
3362                                                                [lastI],
3363                                                                foreY
3364                                                                [cntI
3365                                                                 + 1], 4)) {
3366                                     cntI++;
3367                                 }
3368                                 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3369                             }
3370                             gfx_add_point(im, backX[idxI], backY[idxI]);
3371                             while (idxI > 1) {
3372                                 lastI = idxI;
3373                                 idxI--;
3374                                 while (idxI > 1
3375                                        &&
3376                                        AlmostEqual2sComplement(backY
3377                                                                [lastI],
3378                                                                backY[idxI], 4)
3379                                        &&
3380                                        AlmostEqual2sComplement(backY
3381                                                                [lastI],
3382                                                                backY
3383                                                                [idxI
3384                                                                 - 1], 4)) {
3385                                     idxI--;
3386                                 }
3387                                 gfx_add_point(im, backX[idxI], backY[idxI]);
3388                             }
3389                             idxI = -1;
3390                             drawem = 0;
3391                             gfx_close_path(im);
3392                         }
3393                         if (drawem != 0) {
3394                             drawem = 0;
3395                             idxI = -1;
3396                         }
3397                         if (ii == im->xsize)
3398                             break;
3399                         if (im->slopemode == 0 && ii == 0) {
3400                             continue;
3401                         }
3402                         if (isnan(im->gdes[i].p_data[ii])) {
3403                             drawem = 1;
3404                             continue;
3405                         }
3406                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3407                         if (lastgdes && im->gdes[i].stack) {
3408                             ybase = ytr(im, lastgdes->p_data[ii]);
3409                         } else {
3410                             ybase = ytr(im, areazero);
3411                         }
3412                         if (ybase == ytop) {
3413                             drawem = 1;
3414                             continue;
3415                         }
3417                         if (ybase > ytop) {
3418                             double    extra = ytop;
3420                             ytop = ybase;
3421                             ybase = extra;
3422                         }
3423                         if (im->slopemode == 0) {
3424                             backY[++idxI] = ybase - 0.2;
3425                             backX[idxI] = ii + im->xorigin - 1;
3426                             foreY[idxI] = ytop + 0.2;
3427                             foreX[idxI] = ii + im->xorigin - 1;
3428                         }
3429                         backY[++idxI] = ybase - 0.2;
3430                         backX[idxI] = ii + im->xorigin;
3431                         foreY[idxI] = ytop + 0.2;
3432                         foreX[idxI] = ii + im->xorigin;
3433                     }
3434                     /* close up any remaining area */
3435                     free(foreY);
3436                     free(foreX);
3437                     free(backY);
3438                     free(backX);
3439                 }       /* else GF_LINE */
3440             }
3441             /* if color != 0x0 */
3442             /* make sure we do not run into trouble when stacking on NaN */
3443             for (ii = 0; ii < im->xsize; ii++) {
3444                 if (isnan(im->gdes[i].p_data[ii])) {
3445                     if (lastgdes && (im->gdes[i].stack)) {
3446                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3447                     } else {
3448                         im->gdes[i].p_data[ii] = areazero;
3449                     }
3450                 }
3451             }
3452             lastgdes = &(im->gdes[i]);
3453             break;
3454         case GF_STACK:
3455             rrd_set_error
3456                 ("STACK should already be turned into LINE or AREA here");
3457             return -1;
3458             break;
3459         }               /* switch */
3460     }
3461     cairo_reset_clip(im->cr);
3463     /* grid_paint also does the text */
3464     if (!(im->extra_flags & ONLY_GRAPH))
3465         grid_paint(im);
3466     if (!(im->extra_flags & ONLY_GRAPH))
3467         axis_paint(im);
3468     /* the RULES are the last thing to paint ... */
3469     for (i = 0; i < im->gdes_c; i++) {
3471         switch (im->gdes[i].gf) {
3472         case GF_HRULE:
3473             if (im->gdes[i].yrule >= im->minval
3474                 && im->gdes[i].yrule <= im->maxval) {
3475                 cairo_save(im->cr);
3476                 if (im->gdes[i].dash) {
3477                     cairo_set_dash(im->cr,
3478                                    im->gdes[i].p_dashes,
3479                                    im->gdes[i].ndash, im->gdes[i].offset);
3480                 }
3481                 gfx_line(im, im->xorigin,
3482                          ytr(im, im->gdes[i].yrule),
3483                          im->xorigin + im->xsize,
3484                          ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3485                 cairo_stroke(im->cr);
3486                 cairo_restore(im->cr);
3487             }
3488             break;
3489         case GF_VRULE:
3490             if (im->gdes[i].xrule >= im->start
3491                 && im->gdes[i].xrule <= im->end) {
3492                 cairo_save(im->cr);
3493                 if (im->gdes[i].dash) {
3494                     cairo_set_dash(im->cr,
3495                                    im->gdes[i].p_dashes,
3496                                    im->gdes[i].ndash, im->gdes[i].offset);
3497                 }
3498                 gfx_line(im,
3499                          xtr(im, im->gdes[i].xrule),
3500                          im->yorigin, xtr(im,
3501                                           im->
3502                                           gdes[i].
3503                                           xrule),
3504                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3505                 cairo_stroke(im->cr);
3506                 cairo_restore(im->cr);
3507             }
3508             break;
3509         default:
3510             break;
3511         }
3512     }
3515     switch (im->imgformat) {
3516     case IF_PNG:
3517     {
3518         cairo_status_t status;
3520         status = strlen(im->graphfile) ?
3521             cairo_surface_write_to_png(im->surface, im->graphfile)
3522             : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3523                                                 im);
3525         if (status != CAIRO_STATUS_SUCCESS) {
3526             rrd_set_error("Could not save png to '%s'", im->graphfile);
3527             return 1;
3528         }
3529         break;
3530     }
3531     default:
3532         if (strlen(im->graphfile)) {
3533             cairo_show_page(im->cr);
3534         } else {
3535             cairo_surface_finish(im->surface);
3536         }
3537         break;
3538     }
3540     return 0;
3544 /*****************************************************
3545  * graph stuff 
3546  *****************************************************/
3548 int gdes_alloc(
3549     image_desc_t *im)
3552     im->gdes_c++;
3553     if ((im->gdes = (graph_desc_t *)
3554          rrd_realloc(im->gdes, (im->gdes_c)
3555                      * sizeof(graph_desc_t))) == NULL) {
3556         rrd_set_error("realloc graph_descs");
3557         return -1;
3558     }
3561     im->gdes[im->gdes_c - 1].step = im->step;
3562     im->gdes[im->gdes_c - 1].step_orig = im->step;
3563     im->gdes[im->gdes_c - 1].stack = 0;
3564     im->gdes[im->gdes_c - 1].linewidth = 0;
3565     im->gdes[im->gdes_c - 1].debug = 0;
3566     im->gdes[im->gdes_c - 1].start = im->start;
3567     im->gdes[im->gdes_c - 1].start_orig = im->start;
3568     im->gdes[im->gdes_c - 1].end = im->end;
3569     im->gdes[im->gdes_c - 1].end_orig = im->end;
3570     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3571     im->gdes[im->gdes_c - 1].data = NULL;
3572     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3573     im->gdes[im->gdes_c - 1].data_first = 0;
3574     im->gdes[im->gdes_c - 1].p_data = NULL;
3575     im->gdes[im->gdes_c - 1].rpnp = NULL;
3576     im->gdes[im->gdes_c - 1].p_dashes = NULL;
3577     im->gdes[im->gdes_c - 1].shift = 0.0;
3578     im->gdes[im->gdes_c - 1].dash = 0;
3579     im->gdes[im->gdes_c - 1].ndash = 0;
3580     im->gdes[im->gdes_c - 1].offset = 0;
3581     im->gdes[im->gdes_c - 1].col.red = 0.0;
3582     im->gdes[im->gdes_c - 1].col.green = 0.0;
3583     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3584     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3585     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3586     im->gdes[im->gdes_c - 1].format[0] = '\0';
3587     im->gdes[im->gdes_c - 1].strftm = 0;
3588     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3589     im->gdes[im->gdes_c - 1].ds = -1;
3590     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3591     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3592     im->gdes[im->gdes_c - 1].yrule = DNAN;
3593     im->gdes[im->gdes_c - 1].xrule = 0;
3594     return 0;
3597 /* copies input untill the first unescaped colon is found
3598    or until input ends. backslashes have to be escaped as well */
3599 int scan_for_col(
3600     const char *const input,
3601     int len,
3602     char *const output)
3604     int       inp, outp = 0;
3606     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3607         if (input[inp] == '\\'
3608             && input[inp + 1] != '\0'
3609             && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3610             output[outp++] = input[++inp];
3611         } else {
3612             output[outp++] = input[inp];
3613         }
3614     }
3615     output[outp] = '\0';
3616     return inp;
3619 /* Now just a wrapper around rrd_graph_v */
3620 int rrd_graph(
3621     int argc,
3622     char **argv,
3623     char ***prdata,
3624     int *xsize,
3625     int *ysize,
3626     FILE * stream,
3627     double *ymin,
3628     double *ymax)
3630     int       prlines = 0;
3631     rrd_info_t *grinfo = NULL;
3632     rrd_info_t *walker;
3634     grinfo = rrd_graph_v(argc, argv);
3635     if (grinfo == NULL)
3636         return -1;
3637     walker = grinfo;
3638     (*prdata) = NULL;
3639     while (walker) {
3640         if (strcmp(walker->key, "image_info") == 0) {
3641             prlines++;
3642             if (((*prdata) =
3643                  (char**)(rrd_realloc((*prdata),
3644                              (prlines + 1) * sizeof(char *)))) == NULL) {
3645                 rrd_set_error("realloc prdata");
3646                 return 0;
3647             }
3648             /* imginfo goes to position 0 in the prdata array */
3649             (*prdata)[prlines - 1] = (char*)(malloc((strlen(walker->value.u_str)
3650                                              + 2) * sizeof(char)));
3651             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3652             (*prdata)[prlines] = NULL;
3653         }
3654         /* skip anything else */
3655         walker = walker->next;
3656     }
3657     walker = grinfo;
3658     *xsize = 0;
3659     *ysize = 0;
3660     *ymin = 0;
3661     *ymax = 0;
3662     while (walker) {
3663         if (strcmp(walker->key, "image_width") == 0) {
3664             *xsize = walker->value.u_cnt;
3665         } else if (strcmp(walker->key, "image_height") == 0) {
3666             *ysize = walker->value.u_cnt;
3667         } else if (strcmp(walker->key, "value_min") == 0) {
3668             *ymin = walker->value.u_val;
3669         } else if (strcmp(walker->key, "value_max") == 0) {
3670             *ymax = walker->value.u_val;
3671         } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3672             prlines++;
3673             if (((*prdata) =
3674                  (char**)(rrd_realloc((*prdata),
3675                              (prlines + 1) * sizeof(char *)))) == NULL) {
3676                 rrd_set_error("realloc prdata");
3677                 return 0;
3678             }
3679             (*prdata)[prlines - 1] = (char*)(malloc((strlen(walker->value.u_str)
3680                                              + 2) * sizeof(char)));
3681             (*prdata)[prlines] = NULL;
3682             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3683         } else if (strcmp(walker->key, "image") == 0) {
3684             fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3685                    (stream ? stream : stdout));
3686         }
3687         /* skip anything else */
3688         walker = walker->next;
3689     }
3690     rrd_info_free(grinfo);
3691     return 0;
3695 /* Some surgery done on this function, it became ridiculously big.
3696 ** Things moved:
3697 ** - initializing     now in rrd_graph_init()
3698 ** - options parsing  now in rrd_graph_options()
3699 ** - script parsing   now in rrd_graph_script()
3700 */
3701 rrd_info_t *rrd_graph_v(
3702     int argc,
3703     char **argv)
3705     image_desc_t im;
3706     rrd_info_t *grinfo;
3707     rrd_graph_init(&im);
3708     /* a dummy surface so that we can measure text sizes for placements */
3709     
3710     rrd_graph_options(argc, argv, &im);
3711     if (rrd_test_error()) {
3712         rrd_info_free(im.grinfo);
3713         im_free(&im);
3714         return NULL;
3715     }
3717     if (optind >= argc) {
3718         rrd_info_free(im.grinfo);
3719         im_free(&im);
3720         rrd_set_error("missing filename");
3721         return NULL;
3722     }
3724     if (strlen(argv[optind]) >= MAXPATH) {
3725         rrd_set_error("filename (including path) too long");
3726         rrd_info_free(im.grinfo);
3727         im_free(&im);
3728         return NULL;
3729     }
3731     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3732     im.graphfile[MAXPATH - 1] = '\0';
3734     if (strcmp(im.graphfile, "-") == 0) {
3735         im.graphfile[0] = '\0';
3736     }
3738     rrd_graph_script(argc, argv, &im, 1);
3739     if (rrd_test_error()) {
3740         rrd_info_free(im.grinfo);
3741         im_free(&im);
3742         return NULL;
3743     }
3745     /* Everything is now read and the actual work can start */
3747     if (graph_paint(&im) == -1) {
3748         rrd_info_free(im.grinfo);
3749         im_free(&im);
3750         return NULL;
3751     }
3754     /* The image is generated and needs to be output.
3755      ** Also, if needed, print a line with information about the image.
3756      */
3758     if (im.imginfo) {
3759         rrd_infoval_t info;
3760         char     *path;
3761         char     *filename;
3763         path = strdup(im.graphfile);
3764         filename = basename(path);
3765         info.u_str =
3766             sprintf_alloc(im.imginfo,
3767                           filename,
3768                           (long) (im.zoom *
3769                                   im.ximg), (long) (im.zoom * im.yimg));
3770         grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3771         free(info.u_str);
3772         free(path);
3773     }
3774     if (im.rendered_image) {
3775         rrd_infoval_t img;
3777         img.u_blo.size = im.rendered_image_size;
3778         img.u_blo.ptr = im.rendered_image;
3779         grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3780     }
3781     grinfo = im.grinfo;
3782     im_free(&im);
3783     return grinfo;
3786 static void 
3787 rrd_set_font_desc (
3788     image_desc_t *im,int prop,char *font, double size ){
3789     if (font){
3790         strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);        
3791         im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';   
3792         /* if we already got one, drop it first */
3793         pango_font_description_free(im->text_prop[prop].font_desc);
3794         im->text_prop[prop].font_desc = pango_font_description_from_string( font );        
3795     };
3796     if (size > 0){  
3797         im->text_prop[prop].size = size;
3798     };
3799     if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
3800         pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
3801     };
3804 void rrd_graph_init(
3805     image_desc_t
3806     *im)
3808     unsigned int i;
3809     char     *deffont = getenv("RRD_DEFAULT_FONT");
3810     static PangoFontMap *fontmap = NULL;
3811     PangoContext *context;
3813 #ifdef HAVE_TZSET
3814     tzset();
3815 #endif
3816 #ifdef HAVE_SETLOCALE
3817     setlocale(LC_TIME, "");
3818 #ifdef HAVE_MBSTOWCS
3819     setlocale(LC_CTYPE, "");
3820 #endif
3821 #endif
3822     im->base = 1000;
3823     im->draw_x_grid = 1;
3824     im->draw_y_grid = 1;
3825     im->extra_flags = 0;
3826     im->font_options = cairo_font_options_create();
3827     im->forceleftspace = 0;
3828     im->gdes_c = 0;
3829     im->gdes = NULL;
3830     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3831     im->grid_dash_off = 1;
3832     im->grid_dash_on = 1;
3833     im->gridfit = 1;
3834     im->grinfo = (rrd_info_t *) NULL;
3835     im->grinfo_current = (rrd_info_t *) NULL;
3836     im->imgformat = IF_PNG;
3837     im->imginfo = NULL;
3838     im->lazy = 0;
3839     im->logarithmic = 0;
3840     im->maxval = DNAN;
3841     im->minval = 0;
3842     im->minval = DNAN;
3843     im->prt_c = 0;
3844     im->rigid = 0;
3845     im->rendered_image_size = 0;
3846     im->rendered_image = NULL;
3847     im->slopemode = 0;
3848     im->step = 0;
3849     im->symbol = ' ';
3850     im->tabwidth = 40.0;
3851     im->title[0] = '\0';
3852     im->unitsexponent = 9999;
3853     im->unitslength = 6;
3854     im->viewfactor = 1.0;
3855     im->watermark[0] = '\0';
3856     im->with_markup = 0;
3857     im->ximg = 0;
3858     im->xlab_user.minsec = -1;
3859     im->xorigin = 0;
3860     im->xsize = 400;
3861     im->ygridstep = DNAN;
3862     im->yimg = 0;
3863     im->ylegend[0] = '\0';
3864     im->second_axis_scale = 0; /* 0 disables it */
3865     im->second_axis_shift = 0; /* no shift by default */
3866     im->second_axis_legend[0] = '\0';
3867     im->second_axis_format[0] = '\0'; 
3868     im->yorigin = 0;
3869     im->ysize = 100;
3870     im->zoom = 1;
3872     im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);     
3873     im->cr = cairo_create(im->surface);
3875     for (i = 0; i < DIM(text_prop); i++) {
3876         im->text_prop[i].size = -1;
3877         im->text_prop[i].font_desc = NULL;
3878         rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
3879     }
3881     if (fontmap == NULL){
3882         fontmap = pango_cairo_font_map_get_default();
3883     }
3885     context =  pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
3887     pango_cairo_context_set_resolution(context, 100);
3889     pango_cairo_update_context(im->cr,context);
3891     im->layout = pango_layout_new(context);
3892     g_object_unref (context);
3894 //  im->layout = pango_cairo_create_layout(im->cr);
3897     cairo_font_options_set_hint_style
3898         (im->font_options, CAIRO_HINT_STYLE_FULL);
3899     cairo_font_options_set_hint_metrics
3900         (im->font_options, CAIRO_HINT_METRICS_ON);
3901     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3905     for (i = 0; i < DIM(graph_col); i++)
3906         im->graph_col[i] = graph_col[i];
3912 void rrd_graph_options(
3913     int argc,
3914     char *argv[],
3915     image_desc_t
3916     *im)
3918     int       stroff;
3919     char     *parsetime_error = NULL;
3920     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3921     time_t    start_tmp = 0, end_tmp = 0;
3922     long      long_tmp;
3923     rrd_time_value_t start_tv, end_tv;
3924     long unsigned int color;
3925     char     *old_locale = "";
3927     /* defines for long options without a short equivalent. should be bytes,
3928        and may not collide with (the ASCII value of) short options */
3929 #define LONGOPT_UNITS_SI 255
3931 /* *INDENT-OFF* */
3932     struct option long_options[] = {
3933         { "start",              required_argument, 0, 's'}, 
3934         { "end",                required_argument, 0, 'e'},
3935         { "x-grid",             required_argument, 0, 'x'},
3936         { "y-grid",             required_argument, 0, 'y'},
3937         { "vertical-label",     required_argument, 0, 'v'},
3938         { "width",              required_argument, 0, 'w'},
3939         { "height",             required_argument, 0, 'h'},
3940         { "full-size-mode",     no_argument,       0, 'D'},
3941         { "interlaced",         no_argument,       0, 'i'},
3942         { "upper-limit",        required_argument, 0, 'u'},
3943         { "lower-limit",        required_argument, 0, 'l'},
3944         { "rigid",              no_argument,       0, 'r'},
3945         { "base",               required_argument, 0, 'b'},
3946         { "logarithmic",        no_argument,       0, 'o'},
3947         { "color",              required_argument, 0, 'c'},
3948         { "font",               required_argument, 0, 'n'},
3949         { "title",              required_argument, 0, 't'},
3950         { "imginfo",            required_argument, 0, 'f'},
3951         { "imgformat",          required_argument, 0, 'a'},
3952         { "lazy",               no_argument,       0, 'z'},
3953         { "zoom",               required_argument, 0, 'm'},
3954         { "no-legend",          no_argument,       0, 'g'},
3955         { "force-rules-legend", no_argument,       0, 'F'},
3956         { "only-graph",         no_argument,       0, 'j'},
3957         { "alt-y-grid",         no_argument,       0, 'Y'},
3958         {"disable-rrdtool-tag", no_argument,       0,  1001},
3959         {"right-axis",          required_argument, 0,  1002},
3960         {"right-axis-label",    required_argument, 0,  1003},
3961         {"right-axis-format",   required_argument, 0,  1004},     
3962         { "no-minor",           no_argument,       0, 'I'}, 
3963         { "slope-mode",         no_argument,       0, 'E'},
3964         { "alt-autoscale",      no_argument,       0, 'A'},
3965         { "alt-autoscale-min",  no_argument,       0, 'J'},
3966         { "alt-autoscale-max",  no_argument,       0, 'M'},
3967         { "no-gridfit",         no_argument,       0, 'N'},
3968         { "units-exponent",     required_argument, 0, 'X'},
3969         { "units-length",       required_argument, 0, 'L'},
3970         { "units",              required_argument, 0, LONGOPT_UNITS_SI},
3971         { "step",               required_argument, 0, 'S'},
3972         { "tabwidth",           required_argument, 0, 'T'},
3973         { "font-render-mode",   required_argument, 0, 'R'},
3974         { "graph-render-mode",  required_argument, 0, 'G'},
3975         { "font-smoothing-threshold", required_argument, 0, 'B'},
3976         { "watermark",          required_argument, 0, 'W'},
3977         { "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 */
3978         { "pango-markup",       no_argument,       0, 'P'},
3979         {  0, 0, 0, 0}
3980 };
3981 /* *INDENT-ON* */
3983     optind = 0;
3984     opterr = 0;         /* initialize getopt */
3985     rrd_parsetime("end-24h", &start_tv);
3986     rrd_parsetime("now", &end_tv);
3987     while (1) {
3988         int       option_index = 0;
3989         int       opt;
3990         int       col_start, col_end;
3992         opt = getopt_long(argc, argv,
3993                           "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",
3994                           long_options, &option_index);
3995         if (opt == EOF)
3996             break;
3997         switch (opt) {
3998         case 'I':
3999             im->extra_flags |= NOMINOR;
4000             break;
4001         case 'Y':
4002             im->extra_flags |= ALTYGRID;
4003             break;
4004         case 'A':
4005             im->extra_flags |= ALTAUTOSCALE;
4006             break;
4007         case 'J':
4008             im->extra_flags |= ALTAUTOSCALE_MIN;
4009             break;
4010         case 'M':
4011             im->extra_flags |= ALTAUTOSCALE_MAX;
4012             break;
4013         case 'j':
4014             im->extra_flags |= ONLY_GRAPH;
4015             break;
4016         case 'g':
4017             im->extra_flags |= NOLEGEND;
4018             break;
4019         case 'F':
4020             im->extra_flags |= FORCE_RULES_LEGEND;
4021             break;
4022         case 1001:
4023             im->extra_flags |= NO_RRDTOOL_TAG;
4024             break;              
4025         case LONGOPT_UNITS_SI:
4026             if (im->extra_flags & FORCE_UNITS) {
4027                 rrd_set_error("--units can only be used once!");
4028                 setlocale(LC_NUMERIC, old_locale);
4029                 return;
4030             }
4031             if (strcmp(optarg, "si") == 0)
4032                 im->extra_flags |= FORCE_UNITS_SI;
4033             else {
4034                 rrd_set_error("invalid argument for --units: %s", optarg);
4035                 return;
4036             }
4037             break;
4038         case 'X':
4039             im->unitsexponent = atoi(optarg);
4040             break;
4041         case 'L':
4042             im->unitslength = atoi(optarg);
4043             im->forceleftspace = 1;
4044             break;
4045         case 'T':
4046             old_locale = setlocale(LC_NUMERIC, "C");
4047             im->tabwidth = atof(optarg);
4048             setlocale(LC_NUMERIC, old_locale);
4049             break;
4050         case 'S':
4051             old_locale = setlocale(LC_NUMERIC, "C");
4052             im->step = atoi(optarg);
4053             setlocale(LC_NUMERIC, old_locale);
4054             break;
4055         case 'N':
4056             im->gridfit = 0;
4057             break;
4058         case 'P':
4059             im->with_markup = 1;
4060             break;
4061         case 's':
4062             if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4063                 rrd_set_error("start time: %s", parsetime_error);
4064                 return;
4065             }
4066             break;
4067         case 'e':
4068             if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4069                 rrd_set_error("end time: %s", parsetime_error);
4070                 return;
4071             }
4072             break;
4073         case 'x':
4074             if (strcmp(optarg, "none") == 0) {
4075                 im->draw_x_grid = 0;
4076                 break;
4077             };
4078             if (sscanf(optarg,
4079                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4080                        scan_gtm,
4081                        &im->xlab_user.gridst,
4082                        scan_mtm,
4083                        &im->xlab_user.mgridst,
4084                        scan_ltm,
4085                        &im->xlab_user.labst,
4086                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4087                 strncpy(im->xlab_form, optarg + stroff,
4088                         sizeof(im->xlab_form) - 1);
4089                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4090                 if ((int)
4091                     (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4092                     rrd_set_error("unknown keyword %s", scan_gtm);
4093                     return;
4094                 } else if ((int)
4095                            (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4096                            == -1) {
4097                     rrd_set_error("unknown keyword %s", scan_mtm);
4098                     return;
4099                 } else if ((int)
4100                            (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4101                     rrd_set_error("unknown keyword %s", scan_ltm);
4102                     return;
4103                 }
4104                 im->xlab_user.minsec = 1;
4105                 im->xlab_user.stst = im->xlab_form;
4106             } else {
4107                 rrd_set_error("invalid x-grid format");
4108                 return;
4109             }
4110             break;
4111         case 'y':
4113             if (strcmp(optarg, "none") == 0) {
4114                 im->draw_y_grid = 0;
4115                 break;
4116             };
4117             old_locale = setlocale(LC_NUMERIC, "C");
4118             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4119                 setlocale(LC_NUMERIC, old_locale);
4120                 if (im->ygridstep <= 0) {
4121                     rrd_set_error("grid step must be > 0");
4122                     return;
4123                 } else if (im->ylabfact < 1) {
4124                     rrd_set_error("label factor must be > 0");
4125                     return;
4126                 }
4127             } else {
4128                 setlocale(LC_NUMERIC, old_locale);
4129                 rrd_set_error("invalid y-grid format");
4130                 return;
4131             }
4132             break;
4133         case 1002: /* right y axis */
4135             if(sscanf(optarg,
4136                       "%lf:%lf",
4137                       &im->second_axis_scale,
4138                       &im->second_axis_shift) == 2) {
4139                 if(im->second_axis_scale==0){
4140                     rrd_set_error("the second_axis_scale  must not be 0");
4141                     return;
4142                 }
4143             } else {
4144                 rrd_set_error("invalid right-axis format expected scale:shift");
4145                 return;
4146             }
4147             break;
4148         case 1003:
4149             strncpy(im->second_axis_legend,optarg,150);
4150             im->second_axis_legend[150]='\0';
4151             break;
4152         case 1004:
4153             if (bad_format(optarg)){
4154                 rrd_set_error("use either %le or %lf formats");
4155                 return;
4156             }
4157             strncpy(im->second_axis_format,optarg,150);
4158             im->second_axis_format[150]='\0';
4159             break;
4160         case 'v':
4161             strncpy(im->ylegend, optarg, 150);
4162             im->ylegend[150] = '\0';
4163             break;
4164         case 'u':
4165             old_locale = setlocale(LC_NUMERIC, "C");
4166             im->maxval = atof(optarg);
4167             setlocale(LC_NUMERIC, old_locale);
4168             break;
4169         case 'l':
4170             old_locale = setlocale(LC_NUMERIC, "C");
4171             im->minval = atof(optarg);
4172             setlocale(LC_NUMERIC, old_locale);
4173             break;
4174         case 'b':
4175             im->base = atol(optarg);
4176             if (im->base != 1024 && im->base != 1000) {
4177                 rrd_set_error
4178                     ("the only sensible value for base apart from 1000 is 1024");
4179                 return;
4180             }
4181             break;
4182         case 'w':
4183             long_tmp = atol(optarg);
4184             if (long_tmp < 10) {
4185                 rrd_set_error("width below 10 pixels");
4186                 return;
4187             }
4188             im->xsize = long_tmp;
4189             break;
4190         case 'h':
4191             long_tmp = atol(optarg);
4192             if (long_tmp < 10) {
4193                 rrd_set_error("height below 10 pixels");
4194                 return;
4195             }
4196             im->ysize = long_tmp;
4197             break;
4198         case 'D':
4199             im->extra_flags |= FULL_SIZE_MODE;
4200             break;
4201         case 'i':
4202             /* interlaced png not supported at the moment */
4203             break;
4204         case 'r':
4205             im->rigid = 1;
4206             break;
4207         case 'f':
4208             im->imginfo = optarg;
4209             break;
4210         case 'a':
4211             if ((int)
4212                 (im->imgformat = if_conv(optarg)) == -1) {
4213                 rrd_set_error("unsupported graphics format '%s'", optarg);
4214                 return;
4215             }
4216             break;
4217         case 'z':
4218             im->lazy = 1;
4219             break;
4220         case 'E':
4221             im->slopemode = 1;
4222             break;
4223         case 'o':
4224             im->logarithmic = 1;
4225             break;
4226         case 'c':
4227             if (sscanf(optarg,
4228                        "%10[A-Z]#%n%8lx%n",
4229                        col_nam, &col_start, &color, &col_end) == 2) {
4230                 int       ci;
4231                 int       col_len = col_end - col_start;
4233                 switch (col_len) {
4234                 case 3:
4235                     color =
4236                         (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4237                                                          0x011000) |
4238                          ((color & 0x00F)
4239                           * 0x001100)
4240                          | 0x000000FF);
4241                     break;
4242                 case 4:
4243                     color =
4244                         (((color & 0xF000) *
4245                           0x11000) | ((color & 0x0F00) *
4246                                       0x01100) | ((color &
4247                                                    0x00F0) *
4248                                                   0x00110) |
4249                          ((color & 0x000F) * 0x00011)
4250                         );
4251                     break;
4252                 case 6:
4253                     color = (color << 8) + 0xff /* shift left by 8 */ ;
4254                     break;
4255                 case 8:
4256                     break;
4257                 default:
4258                     rrd_set_error("the color format is #RRGGBB[AA]");
4259                     return;
4260                 }
4261                 if ((ci = grc_conv(col_nam)) != -1) {
4262                     im->graph_col[ci] = gfx_hex_to_col(color);
4263                 } else {
4264                     rrd_set_error("invalid color name '%s'", col_nam);
4265                     return;
4266                 }
4267             } else {
4268                 rrd_set_error("invalid color def format");
4269                 return;
4270             }
4271             break;
4272         case 'n':{
4273             char      prop[15];
4274             double    size = 1;
4275             int       end;
4277             old_locale = setlocale(LC_NUMERIC, "C");
4278             if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4279                 int       sindex, propidx;
4281                 setlocale(LC_NUMERIC, old_locale);
4282                 if ((sindex = text_prop_conv(prop)) != -1) {
4283                     for (propidx = sindex;
4284                          propidx < TEXT_PROP_LAST; propidx++) {
4285                         if (size > 0) {
4286                             rrd_set_font_desc(im,propidx,NULL,size);   
4287                         }
4288                         if ((int) strlen(optarg) > end+2) {
4289                             if (optarg[end] == ':') {
4290                                 rrd_set_font_desc(im,propidx,optarg + end + 1,0);   
4291                             } else {
4292                                 rrd_set_error
4293                                     ("expected : after font size in '%s'",
4294                                      optarg);
4295                                 return;
4296                             }
4297                         }
4298                         /* only run the for loop for DEFAULT (0) for
4299                            all others, we break here. woodo programming */
4300                         if (propidx == sindex && sindex != 0)
4301                             break;
4302                     }
4303                 } else {
4304                     rrd_set_error("invalid fonttag '%s'", prop);
4305                     return;
4306                 }
4307             } else {
4308                 setlocale(LC_NUMERIC, old_locale);
4309                 rrd_set_error("invalid text property format");
4310                 return;
4311             }
4312             break;
4313         }
4314         case 'm':
4315             old_locale = setlocale(LC_NUMERIC, "C");
4316             im->zoom = atof(optarg);
4317             setlocale(LC_NUMERIC, old_locale);
4318             if (im->zoom <= 0.0) {
4319                 rrd_set_error("zoom factor must be > 0");
4320                 return;
4321             }
4322             break;
4323         case 't':
4324             strncpy(im->title, optarg, 150);
4325             im->title[150] = '\0';
4326             break;
4327         case 'R':
4328             if (strcmp(optarg, "normal") == 0) {
4329                 cairo_font_options_set_antialias
4330                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4331                 cairo_font_options_set_hint_style
4332                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4333             } else if (strcmp(optarg, "light") == 0) {
4334                 cairo_font_options_set_antialias
4335                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4336                 cairo_font_options_set_hint_style
4337                     (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4338             } else if (strcmp(optarg, "mono") == 0) {
4339                 cairo_font_options_set_antialias
4340                     (im->font_options, CAIRO_ANTIALIAS_NONE);
4341                 cairo_font_options_set_hint_style
4342                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4343             } else {
4344                 rrd_set_error("unknown font-render-mode '%s'", optarg);
4345                 return;
4346             }
4347             break;
4348         case 'G':
4349             if (strcmp(optarg, "normal") == 0)
4350                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4351             else if (strcmp(optarg, "mono") == 0)
4352                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4353             else {
4354                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4355                 return;
4356             }
4357             break;
4358         case 'B':
4359             /* not supported curently */
4360             break;
4361         case 'W':
4362             strncpy(im->watermark, optarg, 100);
4363             im->watermark[99] = '\0';
4364             break;
4365         case '?':
4366             if (optopt != 0)
4367                 rrd_set_error("unknown option '%c'", optopt);
4368             else
4369                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4370             return;
4371         }
4372     }
4373     
4374     pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4375     pango_layout_context_changed(im->layout);
4379     if (im->logarithmic && im->minval <= 0) {
4380         rrd_set_error
4381             ("for a logarithmic yaxis you must specify a lower-limit > 0");
4382         return;
4383     }
4385     if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4386         /* error string is set in rrd_parsetime.c */
4387         return;
4388     }
4390     if (start_tmp < 3600 * 24 * 365 * 10) {
4391         rrd_set_error
4392             ("the first entry to fetch should be after 1980 (%ld)",
4393              start_tmp);
4394         return;
4395     }
4397     if (end_tmp < start_tmp) {
4398         rrd_set_error
4399             ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4400         return;
4401     }
4403     im->start = start_tmp;
4404     im->end = end_tmp;
4405     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4408 int rrd_graph_color(
4409     image_desc_t
4410     *im,
4411     char *var,
4412     char *err,
4413     int optional)
4415     char     *color;
4416     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4418     color = strstr(var, "#");
4419     if (color == NULL) {
4420         if (optional == 0) {
4421             rrd_set_error("Found no color in %s", err);
4422             return 0;
4423         }
4424         return 0;
4425     } else {
4426         int       n = 0;
4427         char     *rest;
4428         long unsigned int col;
4430         rest = strstr(color, ":");
4431         if (rest != NULL)
4432             n = rest - color;
4433         else
4434             n = strlen(color);
4435         switch (n) {
4436         case 7:
4437             sscanf(color, "#%6lx%n", &col, &n);
4438             col = (col << 8) + 0xff /* shift left by 8 */ ;
4439             if (n != 7)
4440                 rrd_set_error("Color problem in %s", err);
4441             break;
4442         case 9:
4443             sscanf(color, "#%8lx%n", &col, &n);
4444             if (n == 9)
4445                 break;
4446         default:
4447             rrd_set_error("Color problem in %s", err);
4448         }
4449         if (rrd_test_error())
4450             return 0;
4451         gdp->col = gfx_hex_to_col(col);
4452         return n;
4453     }
4457 int bad_format(
4458     char *fmt)
4460     char     *ptr;
4461     int       n = 0;
4463     ptr = fmt;
4464     while (*ptr != '\0')
4465         if (*ptr++ == '%') {
4467             /* line cannot end with percent char */
4468             if (*ptr == '\0')
4469                 return 1;
4470             /* '%s', '%S' and '%%' are allowed */
4471             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4472                 ptr++;
4473             /* %c is allowed (but use only with vdef!) */
4474             else if (*ptr == 'c') {
4475                 ptr++;
4476                 n = 1;
4477             }
4479             /* or else '% 6.2lf' and such are allowed */
4480             else {
4481                 /* optional padding character */
4482                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4483                     ptr++;
4484                 /* This should take care of 'm.n' with all three optional */
4485                 while (*ptr >= '0' && *ptr <= '9')
4486                     ptr++;
4487                 if (*ptr == '.')
4488                     ptr++;
4489                 while (*ptr >= '0' && *ptr <= '9')
4490                     ptr++;
4491                 /* Either 'le', 'lf' or 'lg' must follow here */
4492                 if (*ptr++ != 'l')
4493                     return 1;
4494                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4495                     ptr++;
4496                 else
4497                     return 1;
4498                 n++;
4499             }
4500         }
4502     return (n != 1);
4506 int vdef_parse(
4507     struct graph_desc_t
4508     *gdes,
4509     const char *const str)
4511     /* A VDEF currently is either "func" or "param,func"
4512      * so the parsing is rather simple.  Change if needed.
4513      */
4514     double    param;
4515     char      func[30];
4516     int       n;
4517     char     *old_locale;
4519     n = 0;
4520     old_locale = setlocale(LC_NUMERIC, "C");
4521     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4522     setlocale(LC_NUMERIC, old_locale);
4523     if (n == (int) strlen(str)) {   /* matched */
4524         ;
4525     } else {
4526         n = 0;
4527         sscanf(str, "%29[A-Z]%n", func, &n);
4528         if (n == (int) strlen(str)) {   /* matched */
4529             param = DNAN;
4530         } else {
4531             rrd_set_error
4532                 ("Unknown function string '%s' in VDEF '%s'",
4533                  str, gdes->vname);
4534             return -1;
4535         }
4536     }
4537     if (!strcmp("PERCENT", func))
4538         gdes->vf.op = VDEF_PERCENT;
4539     else if (!strcmp("MAXIMUM", func))
4540         gdes->vf.op = VDEF_MAXIMUM;
4541     else if (!strcmp("AVERAGE", func))
4542         gdes->vf.op = VDEF_AVERAGE;
4543     else if (!strcmp("STDEV", func))
4544         gdes->vf.op = VDEF_STDEV;
4545     else if (!strcmp("MINIMUM", func))
4546         gdes->vf.op = VDEF_MINIMUM;
4547     else if (!strcmp("TOTAL", func))
4548         gdes->vf.op = VDEF_TOTAL;
4549     else if (!strcmp("FIRST", func))
4550         gdes->vf.op = VDEF_FIRST;
4551     else if (!strcmp("LAST", func))
4552         gdes->vf.op = VDEF_LAST;
4553     else if (!strcmp("LSLSLOPE", func))
4554         gdes->vf.op = VDEF_LSLSLOPE;
4555     else if (!strcmp("LSLINT", func))
4556         gdes->vf.op = VDEF_LSLINT;
4557     else if (!strcmp("LSLCORREL", func))
4558         gdes->vf.op = VDEF_LSLCORREL;
4559     else {
4560         rrd_set_error
4561             ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4562         return -1;
4563     };
4564     switch (gdes->vf.op) {
4565     case VDEF_PERCENT:
4566         if (isnan(param)) { /* no parameter given */
4567             rrd_set_error
4568                 ("Function '%s' needs parameter in VDEF '%s'\n",
4569                  func, gdes->vname);
4570             return -1;
4571         };
4572         if (param >= 0.0 && param <= 100.0) {
4573             gdes->vf.param = param;
4574             gdes->vf.val = DNAN;    /* undefined */
4575             gdes->vf.when = 0;  /* undefined */
4576         } else {
4577             rrd_set_error
4578                 ("Parameter '%f' out of range in VDEF '%s'\n",
4579                  param, gdes->vname);
4580             return -1;
4581         };
4582         break;
4583     case VDEF_MAXIMUM:
4584     case VDEF_AVERAGE:
4585     case VDEF_STDEV:
4586     case VDEF_MINIMUM:
4587     case VDEF_TOTAL:
4588     case VDEF_FIRST:
4589     case VDEF_LAST:
4590     case VDEF_LSLSLOPE:
4591     case VDEF_LSLINT:
4592     case VDEF_LSLCORREL:
4593         if (isnan(param)) {
4594             gdes->vf.param = DNAN;
4595             gdes->vf.val = DNAN;
4596             gdes->vf.when = 0;
4597         } else {
4598             rrd_set_error
4599                 ("Function '%s' needs no parameter in VDEF '%s'\n",
4600                  func, gdes->vname);
4601             return -1;
4602         };
4603         break;
4604     };
4605     return 0;
4609 int vdef_calc(
4610     image_desc_t *im,
4611     int gdi)
4613     graph_desc_t *src, *dst;
4614     rrd_value_t *data;
4615     long      step, steps;
4617     dst = &im->gdes[gdi];
4618     src = &im->gdes[dst->vidx];
4619     data = src->data + src->ds;
4621     steps = (src->end - src->start) / src->step;
4622 #if 0
4623     printf
4624         ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4625          src->start, src->end, steps);
4626 #endif
4627     switch (dst->vf.op) {
4628     case VDEF_PERCENT:{
4629         rrd_value_t *array;
4630         int       field;
4631         if ((array = (rrd_value_t*)(malloc(steps * sizeof(double)))) == NULL) {
4632             rrd_set_error("malloc VDEV_PERCENT");
4633             return -1;
4634         }
4635         for (step = 0; step < steps; step++) {
4636             array[step] = data[step * src->ds_cnt];
4637         }
4638         qsort(array, step, sizeof(double), vdef_percent_compar);
4639         field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
4640         dst->vf.val = array[field];
4641         dst->vf.when = 0;   /* no time component */
4642         free(array);
4643 #if 0
4644         for (step = 0; step < steps; step++)
4645             printf("DEBUG: %3li:%10.2f %c\n",
4646                    step, array[step], step == field ? '*' : ' ');
4647 #endif
4648         }
4649         break;
4650     case VDEF_MAXIMUM:
4651         step = 0;
4652         while (step != steps && isnan(data[step * src->ds_cnt]))
4653             step++;
4654         if (step == steps) {
4655             dst->vf.val = DNAN;
4656             dst->vf.when = 0;
4657         } else {
4658             dst->vf.val = data[step * src->ds_cnt];
4659             dst->vf.when = src->start + (step + 1) * src->step;
4660         }
4661         while (step != steps) {
4662             if (finite(data[step * src->ds_cnt])) {
4663                 if (data[step * src->ds_cnt] > dst->vf.val) {
4664                     dst->vf.val = data[step * src->ds_cnt];
4665                     dst->vf.when = src->start + (step + 1) * src->step;
4666                 }
4667             }
4668             step++;
4669         }
4670         break;
4671     case VDEF_TOTAL:
4672     case VDEF_STDEV:
4673     case VDEF_AVERAGE:{
4674         int       cnt = 0;
4675         double    sum = 0.0;
4676         double    average = 0.0;
4678         for (step = 0; step < steps; step++) {
4679             if (finite(data[step * src->ds_cnt])) {
4680                 sum += data[step * src->ds_cnt];
4681                 cnt++;
4682             };
4683         }
4684         if (cnt) {
4685             if (dst->vf.op == VDEF_TOTAL) {
4686                 dst->vf.val = sum * src->step;
4687                 dst->vf.when = 0;   /* no time component */
4688             } else if (dst->vf.op == VDEF_AVERAGE) {
4689                 dst->vf.val = sum / cnt;
4690                 dst->vf.when = 0;   /* no time component */
4691             } else {
4692                 average = sum / cnt;
4693                 sum = 0.0;
4694                 for (step = 0; step < steps; step++) {
4695                     if (finite(data[step * src->ds_cnt])) {
4696                         sum += pow((data[step * src->ds_cnt] - average), 2.0);
4697                     };
4698                 }
4699                 dst->vf.val = pow(sum / cnt, 0.5);
4700                 dst->vf.when = 0;   /* no time component */
4701             };
4702         } else {
4703             dst->vf.val = DNAN;
4704             dst->vf.when = 0;
4705         }
4706     }
4707         break;
4708     case VDEF_MINIMUM:
4709         step = 0;
4710         while (step != steps && isnan(data[step * src->ds_cnt]))
4711             step++;
4712         if (step == steps) {
4713             dst->vf.val = DNAN;
4714             dst->vf.when = 0;
4715         } else {
4716             dst->vf.val = data[step * src->ds_cnt];
4717             dst->vf.when = src->start + (step + 1) * src->step;
4718         }
4719         while (step != steps) {
4720             if (finite(data[step * src->ds_cnt])) {
4721                 if (data[step * src->ds_cnt] < dst->vf.val) {
4722                     dst->vf.val = data[step * src->ds_cnt];
4723                     dst->vf.when = src->start + (step + 1) * src->step;
4724                 }
4725             }
4726             step++;
4727         }
4728         break;
4729     case VDEF_FIRST:
4730         /* The time value returned here is one step before the
4731          * actual time value.  This is the start of the first
4732          * non-NaN interval.
4733          */
4734         step = 0;
4735         while (step != steps && isnan(data[step * src->ds_cnt]))
4736             step++;
4737         if (step == steps) {    /* all entries were NaN */
4738             dst->vf.val = DNAN;
4739             dst->vf.when = 0;
4740         } else {
4741             dst->vf.val = data[step * src->ds_cnt];
4742             dst->vf.when = src->start + step * src->step;
4743         }
4744         break;
4745     case VDEF_LAST:
4746         /* The time value returned here is the
4747          * actual time value.  This is the end of the last
4748          * non-NaN interval.
4749          */
4750         step = steps - 1;
4751         while (step >= 0 && isnan(data[step * src->ds_cnt]))
4752             step--;
4753         if (step < 0) { /* all entries were NaN */
4754             dst->vf.val = DNAN;
4755             dst->vf.when = 0;
4756         } else {
4757             dst->vf.val = data[step * src->ds_cnt];
4758             dst->vf.when = src->start + (step + 1) * src->step;
4759         }
4760         break;
4761     case VDEF_LSLSLOPE:
4762     case VDEF_LSLINT:
4763     case VDEF_LSLCORREL:{
4764         /* Bestfit line by linear least squares method */
4766         int       cnt = 0;
4767         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4769         SUMx = 0;
4770         SUMy = 0;
4771         SUMxy = 0;
4772         SUMxx = 0;
4773         SUMyy = 0;
4774         for (step = 0; step < steps; step++) {
4775             if (finite(data[step * src->ds_cnt])) {
4776                 cnt++;
4777                 SUMx += step;
4778                 SUMxx += step * step;
4779                 SUMxy += step * data[step * src->ds_cnt];
4780                 SUMy += data[step * src->ds_cnt];
4781                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4782             };
4783         }
4785         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4786         y_intercept = (SUMy - slope * SUMx) / cnt;
4787         correl =
4788             (SUMxy -
4789              (SUMx * SUMy) / cnt) /
4790             sqrt((SUMxx -
4791                   (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4792         if (cnt) {
4793             if (dst->vf.op == VDEF_LSLSLOPE) {
4794                 dst->vf.val = slope;
4795                 dst->vf.when = 0;
4796             } else if (dst->vf.op == VDEF_LSLINT) {
4797                 dst->vf.val = y_intercept;
4798                 dst->vf.when = 0;
4799             } else if (dst->vf.op == VDEF_LSLCORREL) {
4800                 dst->vf.val = correl;
4801                 dst->vf.when = 0;
4802             };
4803         } else {
4804             dst->vf.val = DNAN;
4805             dst->vf.when = 0;
4806         }
4807     }
4808         break;
4809     }
4810     return 0;
4813 /* NaN < -INF < finite_values < INF */
4814 int vdef_percent_compar(
4815     const void
4816     *a,
4817     const void
4818     *b)
4820     /* Equality is not returned; this doesn't hurt except
4821      * (maybe) for a little performance.
4822      */
4824     /* First catch NaN values. They are smallest */
4825     if (isnan(*(double *) a))
4826         return -1;
4827     if (isnan(*(double *) b))
4828         return 1;
4829     /* NaN doesn't reach this part so INF and -INF are extremes.
4830      * The sign from isinf() is compatible with the sign we return
4831      */
4832     if (isinf(*(double *) a))
4833         return isinf(*(double *) a);
4834     if (isinf(*(double *) b))
4835         return isinf(*(double *) b);
4836     /* If we reach this, both values must be finite */
4837     if (*(double *) a < *(double *) b)
4838         return -1;
4839     else
4840         return 1;
4843 void grinfo_push(
4844     image_desc_t *im,
4845     char *key,
4846     rrd_info_type_t type,
4847     rrd_infoval_t value)
4849     im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
4850     if (im->grinfo == NULL) {
4851         im->grinfo = im->grinfo_current;
4852     }