Code

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