Code

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