Code

added support for rrdgraph to use the yotta, zetta, zepto, yocto prefixes. Fix for...
[rrdtool.git] / src / rrd_graph.c
1 /****************************************************************************
2  * RRDtool 1.4.3  Copyright by Tobi Oetiker, 1997-2010
3  ****************************************************************************
4  * rrd__graph.c  produce graphs from data in rrdfiles
5  ****************************************************************************/
8 #include <sys/stat.h>
10 #ifdef WIN32
11 #include "strftime.h"
12 #endif
14 #include "rrd_tool.h"
16 /* for basename */
17 #ifdef HAVE_LIBGEN_H
18 #  include <libgen.h>
19 #else
20 #include "plbasename.h"
21 #endif
23 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
24 #include <io.h>
25 #include <fcntl.h>
26 #endif
28 #include <time.h>
30 #include <locale.h>
32 #ifdef HAVE_LANGINFO_H
33 #include <langinfo.h>
34 #endif
36 #include "rrd_graph.h"
37 #include "rrd_client.h"
39 /* some constant definitions */
43 #ifndef RRD_DEFAULT_FONT
44 /* there is special code later to pick Cour.ttf when running on windows */
45 #define RRD_DEFAULT_FONT "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
46 #endif
48 text_prop_t text_prop[] = {
49     {8.0, RRD_DEFAULT_FONT,NULL}
50     ,                   /* default */
51     {9.0, RRD_DEFAULT_FONT,NULL}
52     ,                   /* title */
53     {7.0, RRD_DEFAULT_FONT,NULL}
54     ,                   /* axis */
55     {8.0, RRD_DEFAULT_FONT,NULL}
56     ,                   /* unit */
57     {8.0, RRD_DEFAULT_FONT,NULL} /* legend */
58     ,
59     {5.5, RRD_DEFAULT_FONT,NULL} /* watermark */
60 };
62 char week_fmt[128] = "Week %V";
64 xlab_t    xlab[] = {
65     {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
66     ,
67     {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
68     ,
69     {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
70     ,
71     {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
72     ,
73     {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
74     ,
75     {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
76     ,
77     {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
78     ,
79     {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
80     ,
81     {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
82     ,
83     /*{300,             0,   TMT_HOUR,3,    TMT_HOUR,12,   TMT_HOUR,12,    12*3600,"%a %p"},  this looks silly */
84     {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
85     ,
86     {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
87     ,
88     {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
89     ,
90     {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
91     ,
92     {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, week_fmt}
93     ,
94     {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600, week_fmt}
95     ,
96     {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
97      "%b"}
98     ,
99     {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
100      "%b"}
101     ,
102     {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
103     ,
104     {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
105      365 * 24 * 3600, "%y"}
106     ,
107     {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
108 };
110 /* sensible y label intervals ...*/
112 ylab_t    ylab[] = {
113     {0.1, {1, 2, 5, 10}
114      }
115     ,
116     {0.2, {1, 5, 10, 20}
117      }
118     ,
119     {0.5, {1, 2, 4, 10}
120      }
121     ,
122     {1.0, {1, 2, 5, 10}
123      }
124     ,
125     {2.0, {1, 5, 10, 20}
126      }
127     ,
128     {5.0, {1, 2, 4, 10}
129      }
130     ,
131     {10.0, {1, 2, 5, 10}
132      }
133     ,
134     {20.0, {1, 5, 10, 20}
135      }
136     ,
137     {50.0, {1, 2, 4, 10}
138      }
139     ,
140     {100.0, {1, 2, 5, 10}
141      }
142     ,
143     {200.0, {1, 5, 10, 20}
144      }
145     ,
146     {500.0, {1, 2, 4, 10}
147      }
148     ,
149     {0.0, {0, 0, 0, 0}
150      }
151 };
154 gfx_color_t graph_col[] =   /* default colors */
156     {1.00, 1.00, 1.00, 1.00},   /* canvas     */
157     {0.95, 0.95, 0.95, 1.00},   /* background */
158     {0.81, 0.81, 0.81, 1.00},   /* shade A    */
159     {0.62, 0.62, 0.62, 1.00},   /* shade B    */
160     {0.56, 0.56, 0.56, 0.75},   /* grid       */
161     {0.87, 0.31, 0.31, 0.60},   /* major grid */
162     {0.00, 0.00, 0.00, 1.00},   /* font       */
163     {0.50, 0.12, 0.12, 1.00},   /* arrow      */
164     {0.12, 0.12, 0.12, 1.00},   /* axis       */
165     {0.00, 0.00, 0.00, 1.00}    /* frame      */
166 };
169 /* #define DEBUG */
171 #ifdef DEBUG
172 # define DPRINT(x)    (void)(printf x, printf("\n"))
173 #else
174 # define DPRINT(x)
175 #endif
178 /* initialize with xtr(im,0); */
179 int xtr(
180     image_desc_t *im,
181     time_t mytime)
183     static double pixie;
185     if (mytime == 0) {
186         pixie = (double) im->xsize / (double) (im->end - im->start);
187         return im->xorigin;
188     }
189     return (int) ((double) im->xorigin + pixie * (mytime - im->start));
192 /* translate data values into y coordinates */
193 double ytr(
194     image_desc_t *im,
195     double value)
197     static double pixie;
198     double    yval;
200     if (isnan(value)) {
201         if (!im->logarithmic)
202             pixie = (double) im->ysize / (im->maxval - im->minval);
203         else
204             pixie =
205                 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
206         yval = im->yorigin;
207     } else if (!im->logarithmic) {
208         yval = im->yorigin - pixie * (value - im->minval);
209     } else {
210         if (value < im->minval) {
211             yval = im->yorigin;
212         } else {
213             yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
214         }
215     }
216     return yval;
221 /* conversion function for symbolic entry names */
224 #define conv_if(VV,VVV) \
225    if (strcmp(#VV, string) == 0) return VVV ;
227 enum gf_en gf_conv(
228     char *string)
231     conv_if(PRINT, GF_PRINT);
232     conv_if(GPRINT, GF_GPRINT);
233     conv_if(COMMENT, GF_COMMENT);
234     conv_if(HRULE, GF_HRULE);
235     conv_if(VRULE, GF_VRULE);
236     conv_if(LINE, GF_LINE);
237     conv_if(AREA, GF_AREA);
238         conv_if(GRAD, GF_GRAD);
239     conv_if(STACK, GF_STACK);
240     conv_if(TICK, GF_TICK);
241     conv_if(TEXTALIGN, GF_TEXTALIGN);
242     conv_if(DEF, GF_DEF);
243     conv_if(CDEF, GF_CDEF);
244     conv_if(VDEF, GF_VDEF);
245     conv_if(XPORT, GF_XPORT);
246     conv_if(SHIFT, GF_SHIFT);
248     return (enum gf_en)(-1);
251 enum gfx_if_en if_conv(
252     char *string)
255     conv_if(PNG, IF_PNG);
256     conv_if(SVG, IF_SVG);
257     conv_if(EPS, IF_EPS);
258     conv_if(PDF, IF_PDF);
260     return (enum gfx_if_en)(-1);
263 enum tmt_en tmt_conv(
264     char *string)
267     conv_if(SECOND, TMT_SECOND);
268     conv_if(MINUTE, TMT_MINUTE);
269     conv_if(HOUR, TMT_HOUR);
270     conv_if(DAY, TMT_DAY);
271     conv_if(WEEK, TMT_WEEK);
272     conv_if(MONTH, TMT_MONTH);
273     conv_if(YEAR, TMT_YEAR);
274     return (enum tmt_en)(-1);
277 enum grc_en grc_conv(
278     char *string)
281     conv_if(BACK, GRC_BACK);
282     conv_if(CANVAS, GRC_CANVAS);
283     conv_if(SHADEA, GRC_SHADEA);
284     conv_if(SHADEB, GRC_SHADEB);
285     conv_if(GRID, GRC_GRID);
286     conv_if(MGRID, GRC_MGRID);
287     conv_if(FONT, GRC_FONT);
288     conv_if(ARROW, GRC_ARROW);
289     conv_if(AXIS, GRC_AXIS);
290     conv_if(FRAME, GRC_FRAME);
292     return (enum grc_en)(-1);
295 enum text_prop_en text_prop_conv(
296     char *string)
299     conv_if(DEFAULT, TEXT_PROP_DEFAULT);
300     conv_if(TITLE, TEXT_PROP_TITLE);
301     conv_if(AXIS, TEXT_PROP_AXIS);
302     conv_if(UNIT, TEXT_PROP_UNIT);
303     conv_if(LEGEND, TEXT_PROP_LEGEND);
304     conv_if(WATERMARK, TEXT_PROP_WATERMARK);
305     return (enum text_prop_en)(-1);
309 #undef conv_if
311 int im_free(
312     image_desc_t *im)
314     unsigned long i, ii;
315     cairo_status_t status = (cairo_status_t) 0;
317     if (im == NULL)
318         return 0;
320     if (im->daemon_addr != NULL)
321       free(im->daemon_addr);
323     for (i = 0; i < (unsigned) im->gdes_c; i++) {
324         if (im->gdes[i].data_first) {
325             /* careful here, because a single pointer can occur several times */
326             free(im->gdes[i].data);
327             if (im->gdes[i].ds_namv) {
328                 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
329                     free(im->gdes[i].ds_namv[ii]);
330                 free(im->gdes[i].ds_namv);
331             }
332         }
333         /* free allocated memory used for dashed lines */
334         if (im->gdes[i].p_dashes != NULL)
335             free(im->gdes[i].p_dashes);
337         free(im->gdes[i].p_data);
338         free(im->gdes[i].rpnp);
339     }
340     free(im->gdes);
342     for (i = 0; i < DIM(text_prop);i++){
343         pango_font_description_free(im->text_prop[i].font_desc);
344         im->text_prop[i].font_desc = NULL;
345     }
347     if (im->font_options)
348         cairo_font_options_destroy(im->font_options);
350     if (im->cr) {
351         status = cairo_status(im->cr);
352         cairo_destroy(im->cr);
353     }
356     if (im->rendered_image) {
357         free(im->rendered_image);
358     }
360     if (im->layout) {
361         g_object_unref (im->layout);
362     }
364     if (im->surface)
365         cairo_surface_destroy(im->surface);
367     if (status)
368         fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
369                 cairo_status_to_string(status));
371     return 0;
374 /* find SI magnitude symbol for the given number*/
375 void auto_scale(
376     image_desc_t *im,   /* image description */
377     double *value,
378     char **symb_ptr,
379     double *magfact)
382     char     *symbol[] = { "a", /* 10e-18 Atto */
383         "f",            /* 10e-15 Femto */
384         "p",            /* 10e-12 Pico */
385         "n",            /* 10e-9  Nano */
386         "u",            /* 10e-6  Micro */
387         "m",            /* 10e-3  Milli */
388         " ",            /* Base */
389         "k",            /* 10e3   Kilo */
390         "M",            /* 10e6   Mega */
391         "G",            /* 10e9   Giga */
392         "T",            /* 10e12  Tera */
393         "P",            /* 10e15  Peta */
394         "E"
395     };                  /* 10e18  Exa */
397     int       symbcenter = 6;
398     int       sindex;
400     if (*value == 0.0 || isnan(*value)) {
401         sindex = 0;
402         *magfact = 1.0;
403     } else {
404         sindex = floor(log(fabs(*value)) / log((double) im->base));
405         *magfact = pow((double) im->base, (double) sindex);
406         (*value) /= (*magfact);
407     }
408     if (sindex <= symbcenter && sindex >= -symbcenter) {
409         (*symb_ptr) = symbol[sindex + symbcenter];
410     } else {
411         (*symb_ptr) = "?";
412     }
415 /* power prefixes */
417 static char si_symbol[] = {
418     'y',                /* 10e-24 Yocto */
419     'z',                /* 10e-21 Zepto */
420     'a',                /* 10e-18 Atto */
421     'f',                /* 10e-15 Femto */
422     'p',                /* 10e-12 Pico */
423     'n',                /* 10e-9  Nano */
424     'u',                /* 10e-6  Micro */
425     'm',                /* 10e-3  Milli */
426     ' ',                /* Base */
427     'k',                /* 10e3   Kilo */
428     'M',                /* 10e6   Mega */
429     'G',                /* 10e9   Giga */
430     'T',                /* 10e12  Tera */
431     'P',                /* 10e15  Peta */
432     'E',                /* 10e18  Exa */
433     'Z',                /* 10e21  Zeta */
434     'Y'                 /* 10e24  Yotta */
435 };
436 static const int si_symbcenter = 8;
438 /* find SI magnitude symbol for the numbers on the y-axis*/
439 void si_unit(
440     image_desc_t *im    /* image description */
441     )
444     double    digits, viewdigits = 0;
446     digits =
447         floor(log(max(fabs(im->minval), fabs(im->maxval))) /
448               log((double) im->base));
450     if (im->unitsexponent != 9999) {
451         /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
452         viewdigits = floor((double)(im->unitsexponent / 3));
453     } else {
454         viewdigits = digits;
455     }
457     im->magfact = pow((double) im->base, digits);
459 #ifdef DEBUG
460     printf("digits %6.3f  im->magfact %6.3f\n", digits, im->magfact);
461 #endif
463     im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
465     if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
466         ((viewdigits + si_symbcenter) >= 0))
467         im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
468     else
469         im->symbol = '?';
472 /*  move min and max values around to become sensible */
474 void expand_range(
475     image_desc_t *im)
477     double    sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
478         600.0, 500.0, 400.0, 300.0, 250.0,
479         200.0, 125.0, 100.0, 90.0, 80.0,
480         75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
481         25.0, 20.0, 10.0, 9.0, 8.0,
482         7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
483         2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
484         0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
485     };
487     double    scaled_min, scaled_max;
488     double    adj;
489     int       i;
493 #ifdef DEBUG
494     printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
495            im->minval, im->maxval, im->magfact);
496 #endif
498     if (isnan(im->ygridstep)) {
499         if (im->extra_flags & ALTAUTOSCALE) {
500             /* measure the amplitude of the function. Make sure that
501                graph boundaries are slightly higher then max/min vals
502                so we can see amplitude on the graph */
503             double    delt, fact;
505             delt = im->maxval - im->minval;
506             adj = delt * 0.1;
507             fact = 2.0 * pow(10.0,
508                              floor(log10
509                                    (max(fabs(im->minval), fabs(im->maxval)) /
510                                     im->magfact)) - 2);
511             if (delt < fact) {
512                 adj = (fact - delt) * 0.55;
513 #ifdef DEBUG
514                 printf
515                     ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
516                      im->minval, im->maxval, delt, fact, adj);
517 #endif
518             }
519             im->minval -= adj;
520             im->maxval += adj;
521         } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
522             /* measure the amplitude of the function. Make sure that
523                graph boundaries are slightly lower than min vals
524                so we can see amplitude on the graph */
525             adj = (im->maxval - im->minval) * 0.1;
526             im->minval -= adj;
527         } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
528             /* measure the amplitude of the function. Make sure that
529                graph boundaries are slightly higher than max vals
530                so we can see amplitude on the graph */
531             adj = (im->maxval - im->minval) * 0.1;
532             im->maxval += adj;
533         } else {
534             scaled_min = im->minval / im->magfact;
535             scaled_max = im->maxval / im->magfact;
537             for (i = 1; sensiblevalues[i] > 0; i++) {
538                 if (sensiblevalues[i - 1] >= scaled_min &&
539                     sensiblevalues[i] <= scaled_min)
540                     im->minval = sensiblevalues[i] * (im->magfact);
542                 if (-sensiblevalues[i - 1] <= scaled_min &&
543                     -sensiblevalues[i] >= scaled_min)
544                     im->minval = -sensiblevalues[i - 1] * (im->magfact);
546                 if (sensiblevalues[i - 1] >= scaled_max &&
547                     sensiblevalues[i] <= scaled_max)
548                     im->maxval = sensiblevalues[i - 1] * (im->magfact);
550                 if (-sensiblevalues[i - 1] <= scaled_max &&
551                     -sensiblevalues[i] >= scaled_max)
552                     im->maxval = -sensiblevalues[i] * (im->magfact);
553             }
554         }
555     } else {
556         /* adjust min and max to the grid definition if there is one */
557         im->minval = (double) im->ylabfact * im->ygridstep *
558             floor(im->minval / ((double) im->ylabfact * im->ygridstep));
559         im->maxval = (double) im->ylabfact * im->ygridstep *
560             ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
561     }
563 #ifdef DEBUG
564     fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
565             im->minval, im->maxval, im->magfact);
566 #endif
570 void apply_gridfit(
571     image_desc_t *im)
573     if (isnan(im->minval) || isnan(im->maxval))
574         return;
575     ytr(im, DNAN);
576     if (im->logarithmic) {
577         double    ya, yb, ypix, ypixfrac;
578         double    log10_range = log10(im->maxval) - log10(im->minval);
580         ya = pow((double) 10, floor(log10(im->minval)));
581         while (ya < im->minval)
582             ya *= 10;
583         if (ya > im->maxval)
584             return;     /* don't have y=10^x gridline */
585         yb = ya * 10;
586         if (yb <= im->maxval) {
587             /* we have at least 2 y=10^x gridlines.
588                Make sure distance between them in pixels
589                are an integer by expanding im->maxval */
590             double    y_pixel_delta = ytr(im, ya) - ytr(im, yb);
591             double    factor = y_pixel_delta / floor(y_pixel_delta);
592             double    new_log10_range = factor * log10_range;
593             double    new_ymax_log10 = log10(im->minval) + new_log10_range;
595             im->maxval = pow(10, new_ymax_log10);
596             ytr(im, DNAN);  /* reset precalc */
597             log10_range = log10(im->maxval) - log10(im->minval);
598         }
599         /* make sure first y=10^x gridline is located on
600            integer pixel position by moving scale slightly
601            downwards (sub-pixel movement) */
602         ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
603         ypixfrac = ypix - floor(ypix);
604         if (ypixfrac > 0 && ypixfrac < 1) {
605             double    yfrac = ypixfrac / im->ysize;
607             im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
608             im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
609             ytr(im, DNAN);  /* reset precalc */
610         }
611     } else {
612         /* Make sure we have an integer pixel distance between
613            each minor gridline */
614         double    ypos1 = ytr(im, im->minval);
615         double    ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
616         double    y_pixel_delta = ypos1 - ypos2;
617         double    factor = y_pixel_delta / floor(y_pixel_delta);
618         double    new_range = factor * (im->maxval - im->minval);
619         double    gridstep = im->ygrid_scale.gridstep;
620         double    minor_y, minor_y_px, minor_y_px_frac;
622         if (im->maxval > 0.0)
623             im->maxval = im->minval + new_range;
624         else
625             im->minval = im->maxval - new_range;
626         ytr(im, DNAN);  /* reset precalc */
627         /* make sure first minor gridline is on integer pixel y coord */
628         minor_y = gridstep * floor(im->minval / gridstep);
629         while (minor_y < im->minval)
630             minor_y += gridstep;
631         minor_y_px = ytr(im, minor_y) + im->ysize;  /* ensure > 0 by adding ysize */
632         minor_y_px_frac = minor_y_px - floor(minor_y_px);
633         if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
634             double    yfrac = minor_y_px_frac / im->ysize;
635             double    range = im->maxval - im->minval;
637             im->minval = im->minval - yfrac * range;
638             im->maxval = im->maxval - yfrac * range;
639             ytr(im, DNAN);  /* reset precalc */
640         }
641         calc_horizontal_grid(im);   /* recalc with changed im->maxval */
642     }
645 /* reduce data reimplementation by Alex */
647 void reduce_data(
648     enum cf_en cf,      /* which consolidation function ? */
649     unsigned long cur_step, /* step the data currently is in */
650     time_t *start,      /* start, end and step as requested ... */
651     time_t *end,        /* ... by the application will be   ... */
652     unsigned long *step,    /* ... adjusted to represent reality    */
653     unsigned long *ds_cnt,  /* number of data sources in file */
654     rrd_value_t **data)
655 {                       /* two dimensional array containing the data */
656     int       i, reduce_factor = ceil((double) (*step) / (double) cur_step);
657     unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
658         0;
659     rrd_value_t *srcptr, *dstptr;
661     (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
662     dstptr = *data;
663     srcptr = *data;
664     row_cnt = ((*end) - (*start)) / cur_step;
666 #ifdef DEBUG
667 #define DEBUG_REDUCE
668 #endif
669 #ifdef DEBUG_REDUCE
670     printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
671            row_cnt, reduce_factor, *start, *end, cur_step);
672     for (col = 0; col < row_cnt; col++) {
673         printf("time %10lu: ", *start + (col + 1) * cur_step);
674         for (i = 0; i < *ds_cnt; i++)
675             printf(" %8.2e", srcptr[*ds_cnt * col + i]);
676         printf("\n");
677     }
678 #endif
680     /* We have to combine [reduce_factor] rows of the source
681      ** into one row for the destination.  Doing this we also
682      ** need to take care to combine the correct rows.  First
683      ** alter the start and end time so that they are multiples
684      ** of the new step time.  We cannot reduce the amount of
685      ** time so we have to move the end towards the future and
686      ** the start towards the past.
687      */
688     end_offset = (*end) % (*step);
689     start_offset = (*start) % (*step);
691     /* If there is a start offset (which cannot be more than
692      ** one destination row), skip the appropriate number of
693      ** source rows and one destination row.  The appropriate
694      ** number is what we do know (start_offset/cur_step) of
695      ** the new interval (*step/cur_step aka reduce_factor).
696      */
697 #ifdef DEBUG_REDUCE
698     printf("start_offset: %lu  end_offset: %lu\n", start_offset, end_offset);
699     printf("row_cnt before:  %lu\n", row_cnt);
700 #endif
701     if (start_offset) {
702         (*start) = (*start) - start_offset;
703         skiprows = reduce_factor - start_offset / cur_step;
704         srcptr += skiprows * *ds_cnt;
705         for (col = 0; col < (*ds_cnt); col++)
706             *dstptr++ = DNAN;
707         row_cnt -= skiprows;
708     }
709 #ifdef DEBUG_REDUCE
710     printf("row_cnt between: %lu\n", row_cnt);
711 #endif
713     /* At the end we have some rows that are not going to be
714      ** used, the amount is end_offset/cur_step
715      */
716     if (end_offset) {
717         (*end) = (*end) - end_offset + (*step);
718         skiprows = end_offset / cur_step;
719         row_cnt -= skiprows;
720     }
721 #ifdef DEBUG_REDUCE
722     printf("row_cnt after:   %lu\n", row_cnt);
723 #endif
725 /* Sanity check: row_cnt should be multiple of reduce_factor */
726 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
728     if (row_cnt % reduce_factor) {
729         printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
730                row_cnt, reduce_factor);
731         printf("BUG in reduce_data()\n");
732         exit(1);
733     }
735     /* Now combine reduce_factor intervals at a time
736      ** into one interval for the destination.
737      */
739     for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
740         for (col = 0; col < (*ds_cnt); col++) {
741             rrd_value_t newval = DNAN;
742             unsigned long validval = 0;
744             for (i = 0; i < reduce_factor; i++) {
745                 if (isnan(srcptr[i * (*ds_cnt) + col])) {
746                     continue;
747                 }
748                 validval++;
749                 if (isnan(newval))
750                     newval = srcptr[i * (*ds_cnt) + col];
751                 else {
752                     switch (cf) {
753                     case CF_HWPREDICT:
754                     case CF_MHWPREDICT:
755                     case CF_DEVSEASONAL:
756                     case CF_DEVPREDICT:
757                     case CF_SEASONAL:
758                     case CF_AVERAGE:
759                         newval += srcptr[i * (*ds_cnt) + col];
760                         break;
761                     case CF_MINIMUM:
762                         newval = min(newval, srcptr[i * (*ds_cnt) + col]);
763                         break;
764                     case CF_FAILURES:
765                         /* an interval contains a failure if any subintervals contained a failure */
766                     case CF_MAXIMUM:
767                         newval = max(newval, srcptr[i * (*ds_cnt) + col]);
768                         break;
769                     case CF_LAST:
770                         newval = srcptr[i * (*ds_cnt) + col];
771                         break;
772                     }
773                 }
774             }
775             if (validval == 0) {
776                 newval = DNAN;
777             } else {
778                 switch (cf) {
779                 case CF_HWPREDICT:
780                 case CF_MHWPREDICT:
781                 case CF_DEVSEASONAL:
782                 case CF_DEVPREDICT:
783                 case CF_SEASONAL:
784                 case CF_AVERAGE:
785                     newval /= validval;
786                     break;
787                 case CF_MINIMUM:
788                 case CF_FAILURES:
789                 case CF_MAXIMUM:
790                 case CF_LAST:
791                     break;
792                 }
793             }
794             *dstptr++ = newval;
795         }
796         srcptr += (*ds_cnt) * reduce_factor;
797         row_cnt -= reduce_factor;
798     }
799     /* If we had to alter the endtime, we didn't have enough
800      ** source rows to fill the last row. Fill it with NaN.
801      */
802     if (end_offset)
803         for (col = 0; col < (*ds_cnt); col++)
804             *dstptr++ = DNAN;
805 #ifdef DEBUG_REDUCE
806     row_cnt = ((*end) - (*start)) / *step;
807     srcptr = *data;
808     printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
809            row_cnt, *start, *end, *step);
810     for (col = 0; col < row_cnt; col++) {
811         printf("time %10lu: ", *start + (col + 1) * (*step));
812         for (i = 0; i < *ds_cnt; i++)
813             printf(" %8.2e", srcptr[*ds_cnt * col + i]);
814         printf("\n");
815     }
816 #endif
820 /* get the data required for the graphs from the
821    relevant rrds ... */
823 int data_fetch(
824     image_desc_t *im)
826     int       i, ii;
827     int       skip;
829     /* pull the data from the rrd files ... */
830     for (i = 0; i < (int) im->gdes_c; i++) {
831         /* only GF_DEF elements fetch data */
832         if (im->gdes[i].gf != GF_DEF)
833             continue;
835         skip = 0;
836         /* do we have it already ? */
837         for (ii = 0; ii < i; ii++) {
838             if (im->gdes[ii].gf != GF_DEF)
839                 continue;
840             if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
841                 && (im->gdes[i].cf == im->gdes[ii].cf)
842                 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
843                 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
844                 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
845                 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
846                 /* OK, the data is already there.
847                  ** Just copy the header portion
848                  */
849                 im->gdes[i].start = im->gdes[ii].start;
850                 im->gdes[i].end = im->gdes[ii].end;
851                 im->gdes[i].step = im->gdes[ii].step;
852                 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
853                 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
854                 im->gdes[i].data = im->gdes[ii].data;
855                 im->gdes[i].data_first = 0;
856                 skip = 1;
857             }
858             if (skip)
859                 break;
860         }
861         if (!skip) {
862             unsigned long ft_step = im->gdes[i].step;   /* ft_step will record what we got from fetch */
863             const char *rrd_daemon;
864             int status;
866             if (im->gdes[i].daemon[0] != 0)
867                 rrd_daemon = im->gdes[i].daemon;
868             else
869                 rrd_daemon = im->daemon_addr;
871             /* "daemon" may be NULL. ENV_RRDCACHED_ADDRESS is evaluated in that
872              * case. If "daemon" holds the same value as in the previous
873              * iteration, no actual new connection is established - the
874              * existing connection is re-used. */
875             rrdc_connect (rrd_daemon);
877             /* If connecting was successfull, use the daemon to query the data.
878              * If there is no connection, for example because no daemon address
879              * was specified, (try to) use the local file directly. */
880             if (rrdc_is_connected (rrd_daemon))
881             {
882                 status = rrdc_fetch (im->gdes[i].rrd,
883                         cf_to_string (im->gdes[i].cf),
884                         &im->gdes[i].start,
885                         &im->gdes[i].end,
886                         &ft_step,
887                         &im->gdes[i].ds_cnt,
888                         &im->gdes[i].ds_namv,
889                         &im->gdes[i].data);
890                 if (status != 0)
891                     return (status);
892             }
893             else
894             {
895                 if ((rrd_fetch_fn(im->gdes[i].rrd,
896                                 im->gdes[i].cf,
897                                 &im->gdes[i].start,
898                                 &im->gdes[i].end,
899                                 &ft_step,
900                                 &im->gdes[i].ds_cnt,
901                                 &im->gdes[i].ds_namv,
902                                 &im->gdes[i].data)) == -1) {
903                     return -1;                      
904                 }               
905             }
906             im->gdes[i].data_first = 1;
908             /* must reduce to at least im->step
909                otherwhise we end up with more data than we can handle in the 
910                chart and visibility of data will be random */            
911             im->gdes[i].step = max(im->gdes[i].step,im->step);
912             if (ft_step < im->gdes[i].step) {
913                 reduce_data(im->gdes[i].cf_reduce,
914                             ft_step,
915                             &im->gdes[i].start,
916                             &im->gdes[i].end,
917                             &im->gdes[i].step,
918                             &im->gdes[i].ds_cnt, &im->gdes[i].data);
919             } else {
920                 im->gdes[i].step = ft_step;
921             }
922         }
924         /* lets see if the required data source is really there */
925         for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
926             if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
927                 im->gdes[i].ds = ii;
928             }
929         }
930         if (im->gdes[i].ds == -1) {
931             rrd_set_error("No DS called '%s' in '%s'",
932                           im->gdes[i].ds_nam, im->gdes[i].rrd);
933             return -1;
934         }
936     }
937     return 0;
940 /* evaluate the expressions in the CDEF functions */
942 /*************************************************************
943  * CDEF stuff
944  *************************************************************/
946 long find_var_wrapper(
947     void *arg1,
948     char *key)
950     return find_var((image_desc_t *) arg1, key);
953 /* find gdes containing var*/
954 long find_var(
955     image_desc_t *im,
956     char *key)
958     long      ii;
960     for (ii = 0; ii < im->gdes_c - 1; ii++) {
961         if ((im->gdes[ii].gf == GF_DEF
962              || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
963             && (strcmp(im->gdes[ii].vname, key) == 0)) {
964             return ii;
965         }
966     }
967     return -1;
970 /* find the greatest common divisor for all the numbers
971    in the 0 terminated num array */
972 long lcd(
973     long *num)
975     long      rest;
976     int       i;
978     for (i = 0; num[i + 1] != 0; i++) {
979         do {
980             rest = num[i] % num[i + 1];
981             num[i] = num[i + 1];
982             num[i + 1] = rest;
983         } while (rest != 0);
984         num[i + 1] = num[i];
985     }
986 /*    return i==0?num[i]:num[i-1]; */
987     return num[i];
990 /* run the rpn calculator on all the VDEF and CDEF arguments */
991 int data_calc(
992     image_desc_t *im)
995     int       gdi;
996     int       dataidx;
997     long     *steparray, rpi;
998     int       stepcnt;
999     time_t    now;
1000     rpnstack_t rpnstack;
1002     rpnstack_init(&rpnstack);
1004     for (gdi = 0; gdi < im->gdes_c; gdi++) {
1005         /* Look for GF_VDEF and GF_CDEF in the same loop,
1006          * so CDEFs can use VDEFs and vice versa
1007          */
1008         switch (im->gdes[gdi].gf) {
1009         case GF_XPORT:
1010             break;
1011         case GF_SHIFT:{
1012             graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
1014             /* remove current shift */
1015             vdp->start -= vdp->shift;
1016             vdp->end -= vdp->shift;
1018             /* vdef */
1019             if (im->gdes[gdi].shidx >= 0)
1020                 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
1021             /* constant */
1022             else
1023                 vdp->shift = im->gdes[gdi].shval;
1025             /* normalize shift to multiple of consolidated step */
1026             vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
1028             /* apply shift */
1029             vdp->start += vdp->shift;
1030             vdp->end += vdp->shift;
1031             break;
1032         }
1033         case GF_VDEF:
1034             /* A VDEF has no DS.  This also signals other parts
1035              * of rrdtool that this is a VDEF value, not a CDEF.
1036              */
1037             im->gdes[gdi].ds_cnt = 0;
1038             if (vdef_calc(im, gdi)) {
1039                 rrd_set_error("Error processing VDEF '%s'",
1040                               im->gdes[gdi].vname);
1041                 rpnstack_free(&rpnstack);
1042                 return -1;
1043             }
1044             break;
1045         case GF_CDEF:
1046             im->gdes[gdi].ds_cnt = 1;
1047             im->gdes[gdi].ds = 0;
1048             im->gdes[gdi].data_first = 1;
1049             im->gdes[gdi].start = 0;
1050             im->gdes[gdi].end = 0;
1051             steparray = NULL;
1052             stepcnt = 0;
1053             dataidx = -1;
1055             /* Find the variables in the expression.
1056              * - VDEF variables are substituted by their values
1057              *   and the opcode is changed into OP_NUMBER.
1058              * - CDEF variables are analized for their step size,
1059              *   the lowest common denominator of all the step
1060              *   sizes of the data sources involved is calculated
1061              *   and the resulting number is the step size for the
1062              *   resulting data source.
1063              */
1064             for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1065                 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1066                     im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1067                     long      ptr = im->gdes[gdi].rpnp[rpi].ptr;
1069                     if (im->gdes[ptr].ds_cnt == 0) {    /* this is a VDEF data source */
1070 #if 0
1071                         printf
1072                             ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1073                              im->gdes[gdi].vname, im->gdes[ptr].vname);
1074                         printf("DEBUG: value from vdef is %f\n",
1075                                im->gdes[ptr].vf.val);
1076 #endif
1077                         im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1078                         im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1079                     } else {    /* normal variables and PREF(variables) */
1081                         /* add one entry to the array that keeps track of the step sizes of the
1082                          * data sources going into the CDEF. */
1083                         if ((steparray =
1084                              (long*)rrd_realloc(steparray,
1085                                          (++stepcnt +
1086                                           1) * sizeof(*steparray))) == NULL) {
1087                             rrd_set_error("realloc steparray");
1088                             rpnstack_free(&rpnstack);
1089                             return -1;
1090                         };
1092                         steparray[stepcnt - 1] = im->gdes[ptr].step;
1094                         /* adjust start and end of cdef (gdi) so
1095                          * that it runs from the latest start point
1096                          * to the earliest endpoint of any of the
1097                          * rras involved (ptr)
1098                          */
1100                         if (im->gdes[gdi].start < im->gdes[ptr].start)
1101                             im->gdes[gdi].start = im->gdes[ptr].start;
1103                         if (im->gdes[gdi].end == 0 ||
1104                             im->gdes[gdi].end > im->gdes[ptr].end)
1105                             im->gdes[gdi].end = im->gdes[ptr].end;
1107                         /* store pointer to the first element of
1108                          * the rra providing data for variable,
1109                          * further save step size and data source
1110                          * count of this rra
1111                          */
1112                         im->gdes[gdi].rpnp[rpi].data =
1113                             im->gdes[ptr].data + im->gdes[ptr].ds;
1114                         im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1115                         im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1117                         /* backoff the *.data ptr; this is done so
1118                          * rpncalc() function doesn't have to treat
1119                          * the first case differently
1120                          */
1121                     }   /* if ds_cnt != 0 */
1122                 }       /* if OP_VARIABLE */
1123             }           /* loop through all rpi */
1125             /* move the data pointers to the correct period */
1126             for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1127                 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1128                     im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1129                     long      ptr = im->gdes[gdi].rpnp[rpi].ptr;
1130                     long      diff =
1131                         im->gdes[gdi].start - im->gdes[ptr].start;
1133                     if (diff > 0)
1134                         im->gdes[gdi].rpnp[rpi].data +=
1135                             (diff / im->gdes[ptr].step) *
1136                             im->gdes[ptr].ds_cnt;
1137                 }
1138             }
1140             if (steparray == NULL) {
1141                 rrd_set_error("rpn expressions without DEF"
1142                               " or CDEF variables are not supported");
1143                 rpnstack_free(&rpnstack);
1144                 return -1;
1145             }
1146             steparray[stepcnt] = 0;
1147             /* Now find the resulting step.  All steps in all
1148              * used RRAs have to be visited
1149              */
1150             im->gdes[gdi].step = lcd(steparray);
1151             free(steparray);
1152             if ((im->gdes[gdi].data = (rrd_value_t*)malloc(((im->gdes[gdi].end -
1153                                                im->gdes[gdi].start)
1154                                               / im->gdes[gdi].step)
1155                                              * sizeof(double))) == NULL) {
1156                 rrd_set_error("malloc im->gdes[gdi].data");
1157                 rpnstack_free(&rpnstack);
1158                 return -1;
1159             }
1161             /* Step through the new cdef results array and
1162              * calculate the values
1163              */
1164             for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1165                  now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1166                 rpnp_t   *rpnp = im->gdes[gdi].rpnp;
1168                 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1169                  * in this case we are advancing by timesteps;
1170                  * we use the fact that time_t is a synonym for long
1171                  */
1172                 if (rpn_calc(rpnp, &rpnstack, (long) now,
1173                              im->gdes[gdi].data, ++dataidx) == -1) {
1174                     /* rpn_calc sets the error string */
1175                     rpnstack_free(&rpnstack);
1176                     return -1;
1177                 }
1178             }           /* enumerate over time steps within a CDEF */
1179             break;
1180         default:
1181             continue;
1182         }
1183     }                   /* enumerate over CDEFs */
1184     rpnstack_free(&rpnstack);
1185     return 0;
1188 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1189 /* yes we are loosing precision by doing tos with floats instead of doubles
1190    but it seems more stable this way. */
1192 static int AlmostEqual2sComplement(
1193     float A,
1194     float B,
1195     int maxUlps)
1198     int       aInt = *(int *) &A;
1199     int       bInt = *(int *) &B;
1200     int       intDiff;
1202     /* Make sure maxUlps is non-negative and small enough that the
1203        default NAN won't compare as equal to anything.  */
1205     /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1207     /* Make aInt lexicographically ordered as a twos-complement int */
1209     if (aInt < 0)
1210         aInt = 0x80000000l - aInt;
1212     /* Make bInt lexicographically ordered as a twos-complement int */
1214     if (bInt < 0)
1215         bInt = 0x80000000l - bInt;
1217     intDiff = abs(aInt - bInt);
1219     if (intDiff <= maxUlps)
1220         return 1;
1222     return 0;
1225 /* massage data so, that we get one value for each x coordinate in the graph */
1226 int data_proc(
1227     image_desc_t *im)
1229     long      i, ii;
1230     double    pixstep = (double) (im->end - im->start)
1231         / (double) im->xsize;   /* how much time
1232                                    passes in one pixel */
1233     double    paintval;
1234     double    minval = DNAN, maxval = DNAN;
1236     unsigned long gr_time;
1238     /* memory for the processed data */
1239     for (i = 0; i < im->gdes_c; i++) {
1240         if ((im->gdes[i].gf == GF_LINE)
1241          || (im->gdes[i].gf == GF_AREA) 
1242          || (im->gdes[i].gf == GF_TICK)
1243          || (im->gdes[i].gf == GF_GRAD)
1244         ) {
1245             if ((im->gdes[i].p_data = (rrd_value_t*)malloc((im->xsize + 1)
1246                                              * sizeof(rrd_value_t))) == NULL) {
1247                 rrd_set_error("malloc data_proc");
1248                 return -1;
1249             }
1250         }
1251     }
1253     for (i = 0; i < im->xsize; i++) {   /* for each pixel */
1254         long      vidx;
1256         gr_time = im->start + pixstep * i;  /* time of the current step */
1257         paintval = 0.0;
1259         for (ii = 0; ii < im->gdes_c; ii++) {
1260             double    value;
1262             switch (im->gdes[ii].gf) {
1263             case GF_LINE:
1264             case GF_AREA:
1265                         case GF_GRAD:
1266             case GF_TICK:
1267                 if (!im->gdes[ii].stack)
1268                     paintval = 0.0;
1269                 value = im->gdes[ii].yrule;
1270                 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1271                     /* The time of the data doesn't necessarily match
1272                      ** the time of the graph. Beware.
1273                      */
1274                     vidx = im->gdes[ii].vidx;
1275                     if (im->gdes[vidx].gf == GF_VDEF) {
1276                         value = im->gdes[vidx].vf.val;
1277                     } else
1278                         if (((long int) gr_time >=
1279                              (long int) im->gdes[vidx].start)
1280                             && ((long int) gr_time <
1281                                 (long int) im->gdes[vidx].end)) {
1282                         value = im->gdes[vidx].data[(unsigned long)
1283                                                     floor((double)
1284                                                           (gr_time -
1285                                                            im->gdes[vidx].
1286                                                            start)
1287                                                           /
1288                                                           im->gdes[vidx].step)
1289                                                     * im->gdes[vidx].ds_cnt +
1290                                                     im->gdes[vidx].ds];
1291                     } else {
1292                         value = DNAN;
1293                     }
1294                 };
1296                 if (!isnan(value)) {
1297                     paintval += value;
1298                     im->gdes[ii].p_data[i] = paintval;
1299                     /* GF_TICK: the data values are not
1300                      ** relevant for min and max
1301                      */
1302                     if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1303                         if ((isnan(minval) || paintval < minval) &&
1304                             !(im->logarithmic && paintval <= 0.0))
1305                             minval = paintval;
1306                         if (isnan(maxval) || paintval > maxval)
1307                             maxval = paintval;
1308                     }
1309                 } else {
1310                     im->gdes[ii].p_data[i] = DNAN;
1311                 }
1312                 break;
1313             case GF_STACK:
1314                 rrd_set_error
1315                     ("STACK should already be turned into LINE or AREA here");
1316                 return -1;
1317                 break;
1318             default:
1319                 break;
1320             }
1321         }
1322     }
1324     /* if min or max have not been asigned a value this is because
1325        there was no data in the graph ... this is not good ...
1326        lets set these to dummy values then ... */
1328     if (im->logarithmic) {
1329         if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1330             minval = 0.0;   /* catching this right away below */
1331             maxval = 5.1;
1332         }
1333         /* in logarithm mode, where minval is smaller or equal
1334            to 0 make the beast just way smaller than maxval */
1335         if (minval <= 0) {
1336             minval = maxval / 10e8;
1337         }
1338     } else {
1339         if (isnan(minval) || isnan(maxval)) {
1340             minval = 0.0;
1341             maxval = 1.0;
1342         }
1343     }
1345     /* adjust min and max values given by the user */
1346     /* for logscale we add something on top */
1347     if (isnan(im->minval)
1348         || ((!im->rigid) && im->minval > minval)
1349         ) {
1350         if (im->logarithmic)
1351             im->minval = minval / 2.0;
1352         else
1353             im->minval = minval;
1354     }
1355     if (isnan(im->maxval)
1356         || (!im->rigid && im->maxval < maxval)
1357         ) {
1358         if (im->logarithmic)
1359             im->maxval = maxval * 2.0;
1360         else
1361             im->maxval = maxval;
1362     }
1364     /* make sure min is smaller than max */
1365     if (im->minval > im->maxval) {
1366         if (im->minval > 0)
1367             im->minval = 0.99 * im->maxval;
1368         else
1369             im->minval = 1.01 * im->maxval;
1370     }
1372     /* make sure min and max are not equal */
1373     if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1374         if (im->maxval > 0)
1375             im->maxval *= 1.01;
1376         else
1377             im->maxval *= 0.99;
1379         /* make sure min and max are not both zero */
1380         if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1381             im->maxval = 1.0;
1382         }
1383     }
1384     return 0;
1387 static int find_first_weekday(void){
1388     static int first_weekday = -1;
1389     if (first_weekday == -1){
1390 #ifdef HAVE__NL_TIME_WEEK_1STDAY
1391         /* according to http://sourceware.org/ml/libc-locales/2009-q1/msg00011.html */
1392         long week_1stday_l = (long) nl_langinfo (_NL_TIME_WEEK_1STDAY);
1393         if (week_1stday_l == 19971130) first_weekday = 0; /* Sun */
1394         else if (week_1stday_l == 19971201) first_weekday = 1; /* Mon */
1395         else first_weekday = 1; /* we go for a monday default */
1396 #else
1397         first_weekday = 1;
1398 #endif
1399     }
1400     return first_weekday;
1403 /* identify the point where the first gridline, label ... gets placed */
1405 time_t find_first_time(
1406     time_t start,       /* what is the initial time */
1407     enum tmt_en baseint,    /* what is the basic interval */
1408     long basestep       /* how many if these do we jump a time */
1409     )
1411     struct tm tm;
1413     localtime_r(&start, &tm);
1415     switch (baseint) {
1416     case TMT_SECOND:
1417         tm.       tm_sec -= tm.tm_sec % basestep;
1419         break;
1420     case TMT_MINUTE:
1421         tm.       tm_sec = 0;
1422         tm.       tm_min -= tm.tm_min % basestep;
1424         break;
1425     case TMT_HOUR:
1426         tm.       tm_sec = 0;
1427         tm.       tm_min = 0;
1428         tm.       tm_hour -= tm.tm_hour % basestep;
1430         break;
1431     case TMT_DAY:
1432         /* we do NOT look at the basestep for this ... */
1433         tm.       tm_sec = 0;
1434         tm.       tm_min = 0;
1435         tm.       tm_hour = 0;
1437         break;
1438     case TMT_WEEK:
1439         /* we do NOT look at the basestep for this ... */
1440         tm.       tm_sec = 0;
1441         tm.       tm_min = 0;
1442         tm.       tm_hour = 0;
1443         tm.       tm_mday -= tm.tm_wday - find_first_weekday();
1445         if (tm.tm_wday == 0 && find_first_weekday() > 0)
1446             tm.       tm_mday -= 7; /* we want the *previous* week */
1448         break;
1449     case TMT_MONTH:
1450         tm.       tm_sec = 0;
1451         tm.       tm_min = 0;
1452         tm.       tm_hour = 0;
1453         tm.       tm_mday = 1;
1454         tm.       tm_mon -= tm.tm_mon % basestep;
1456         break;
1458     case TMT_YEAR:
1459         tm.       tm_sec = 0;
1460         tm.       tm_min = 0;
1461         tm.       tm_hour = 0;
1462         tm.       tm_mday = 1;
1463         tm.       tm_mon = 0;
1464         tm.       tm_year -= (
1465     tm.tm_year + 1900) %basestep;
1467     }
1468     return mktime(&tm);
1471 /* identify the point where the next gridline, label ... gets placed */
1472 time_t find_next_time(
1473     time_t current,     /* what is the initial time */
1474     enum tmt_en baseint,    /* what is the basic interval */
1475     long basestep       /* how many if these do we jump a time */
1476     )
1478     struct tm tm;
1479     time_t    madetime;
1481     localtime_r(&current, &tm);
1483     int limit = 2;
1484     switch (baseint) {
1485     case TMT_SECOND: limit = 7200; break;
1486     case TMT_MINUTE: limit = 120; break;
1487     case TMT_HOUR: limit = 2; break;
1488     default: limit = 2; break;
1489     }
1490     do {
1491         switch (baseint) {
1492         case TMT_SECOND:
1493             tm.       tm_sec += basestep;
1495             break;
1496         case TMT_MINUTE:
1497             tm.       tm_min += basestep;
1499             break;
1500         case TMT_HOUR:
1501             tm.       tm_hour += basestep;
1503             break;
1504         case TMT_DAY:
1505             tm.       tm_mday += basestep;
1507             break;
1508         case TMT_WEEK:
1509             tm.       tm_mday += 7 * basestep;
1511             break;
1512         case TMT_MONTH:
1513             tm.       tm_mon += basestep;
1515             break;
1516         case TMT_YEAR:
1517             tm.       tm_year += basestep;
1518         }
1519         madetime = mktime(&tm);
1520     } while (madetime == -1 && limit-- >= 0);   /* this is necessary to skip impossible times
1521                                    like the daylight saving time skips */
1522     return madetime;
1527 /* calculate values required for PRINT and GPRINT functions */
1529 int print_calc(
1530     image_desc_t *im)
1532     long      i, ii, validsteps;
1533     double    printval;
1534     struct tm tmvdef;
1535     int       graphelement = 0;
1536     long      vidx;
1537     int       max_ii;
1538     double    magfact = -1;
1539     char     *si_symb = "";
1540     char     *percent_s;
1541     int       prline_cnt = 0;
1543     /* wow initializing tmvdef is quite a task :-) */
1544     time_t    now = time(NULL);
1546     localtime_r(&now, &tmvdef);
1547     for (i = 0; i < im->gdes_c; i++) {
1548         vidx = im->gdes[i].vidx;
1549         switch (im->gdes[i].gf) {
1550         case GF_PRINT:
1551         case GF_GPRINT:
1552             /* PRINT and GPRINT can now print VDEF generated values.
1553              * There's no need to do any calculations on them as these
1554              * calculations were already made.
1555              */
1556             if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1557                 printval = im->gdes[vidx].vf.val;
1558                 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1559             } else {    /* need to calculate max,min,avg etcetera */
1560                 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1561                           / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1562                 printval = DNAN;
1563                 validsteps = 0;
1564                 for (ii = im->gdes[vidx].ds;
1565                      ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1566                     if (!finite(im->gdes[vidx].data[ii]))
1567                         continue;
1568                     if (isnan(printval)) {
1569                         printval = im->gdes[vidx].data[ii];
1570                         validsteps++;
1571                         continue;
1572                     }
1574                     switch (im->gdes[i].cf) {
1575                     case CF_HWPREDICT:
1576                     case CF_MHWPREDICT:
1577                     case CF_DEVPREDICT:
1578                     case CF_DEVSEASONAL:
1579                     case CF_SEASONAL:
1580                     case CF_AVERAGE:
1581                         validsteps++;
1582                         printval += im->gdes[vidx].data[ii];
1583                         break;
1584                     case CF_MINIMUM:
1585                         printval = min(printval, im->gdes[vidx].data[ii]);
1586                         break;
1587                     case CF_FAILURES:
1588                     case CF_MAXIMUM:
1589                         printval = max(printval, im->gdes[vidx].data[ii]);
1590                         break;
1591                     case CF_LAST:
1592                         printval = im->gdes[vidx].data[ii];
1593                     }
1594                 }
1595                 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1596                     if (validsteps > 1) {
1597                         printval = (printval / validsteps);
1598                     }
1599                 }
1600             }           /* prepare printval */
1602             if (!im->gdes[i].strftm && (percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1603                 /* Magfact is set to -1 upon entry to print_calc.  If it
1604                  * is still less than 0, then we need to run auto_scale.
1605                  * Otherwise, put the value into the correct units.  If
1606                  * the value is 0, then do not set the symbol or magnification
1607                  * so next the calculation will be performed again. */
1608                 if (magfact < 0.0) {
1609                     auto_scale(im, &printval, &si_symb, &magfact);
1610                     if (printval == 0.0)
1611                         magfact = -1.0;
1612                 } else {
1613                     printval /= magfact;
1614                 }
1615                 *(++percent_s) = 's';
1616             } else if (!im->gdes[i].strftm && strstr(im->gdes[i].format, "%s") != NULL) {
1617                 auto_scale(im, &printval, &si_symb, &magfact);
1618             }
1620             if (im->gdes[i].gf == GF_PRINT) {
1621                 rrd_infoval_t prline;
1623                 if (im->gdes[i].strftm) {
1624                     prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1625                     if (im->gdes[vidx].vf.never == 1) {
1626                        time_clean(prline.u_str, im->gdes[i].format);
1627                     } else {
1628                         strftime(prline.u_str,
1629                                  FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1630                     }
1631                 } else if (bad_format(im->gdes[i].format)) {
1632                     rrd_set_error
1633                         ("bad format for PRINT in '%s'", im->gdes[i].format);
1634                     return -1;
1635                 } else {
1636                     prline.u_str =
1637                         sprintf_alloc(im->gdes[i].format, printval, si_symb);
1638                 }
1639                 grinfo_push(im,
1640                             sprintf_alloc
1641                             ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1642                 free(prline.u_str);
1643             } else {
1644                 /* GF_GPRINT */
1646                 if (im->gdes[i].strftm) {
1647                     if (im->gdes[vidx].vf.never == 1) {
1648                        time_clean(im->gdes[i].legend, im->gdes[i].format);
1649                     } else {
1650                         strftime(im->gdes[i].legend,
1651                                  FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1652                     }
1653                 } else {
1654                     if (bad_format(im->gdes[i].format)) {
1655                         rrd_set_error
1656                             ("bad format for GPRINT in '%s'",
1657                              im->gdes[i].format);
1658                         return -1;
1659                     }
1660 #ifdef HAVE_SNPRINTF
1661                     snprintf(im->gdes[i].legend,
1662                              FMT_LEG_LEN - 2,
1663                              im->gdes[i].format, printval, si_symb);
1664 #else
1665                     sprintf(im->gdes[i].legend,
1666                             im->gdes[i].format, printval, si_symb);
1667 #endif
1668                 }
1669                 graphelement = 1;
1670             }
1671             break;
1672         case GF_LINE:
1673         case GF_AREA:
1674                 case GF_GRAD:
1675         case GF_TICK:
1676             graphelement = 1;
1677             break;
1678         case GF_HRULE:
1679             if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1680                 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1681             };
1682             graphelement = 1;
1683             break;
1684         case GF_VRULE:
1685             if (im->gdes[i].xrule == 0) {   /* again ... the legend printer needs it */
1686                 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1687             };
1688             graphelement = 1;
1689             break;
1690         case GF_COMMENT:
1691         case GF_TEXTALIGN:
1692         case GF_DEF:
1693         case GF_CDEF:
1694         case GF_VDEF:
1695 #ifdef WITH_PIECHART
1696         case GF_PART:
1697 #endif
1698         case GF_SHIFT:
1699         case GF_XPORT:
1700             break;
1701         case GF_STACK:
1702             rrd_set_error
1703                 ("STACK should already be turned into LINE or AREA here");
1704             return -1;
1705             break;
1706         }
1707     }
1708     return graphelement;
1713 /* place legends with color spots */
1714 int leg_place(
1715     image_desc_t *im,
1716     int calc_width)
1718     /* graph labels */
1719     int       interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1720     int       border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1721     int       fill = 0, fill_last;
1722     double    legendwidth; // = im->ximg - 2 * border;
1723     int       leg_c = 0;
1724     double    leg_x = border;
1725     int       leg_y = 0; //im->yimg;
1726     int       leg_y_prev = 0; // im->yimg;
1727     int       leg_cc;
1728     double    glue = 0;
1729     int       i, ii, mark = 0;
1730     char      default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1731     int      *legspace;
1732     char     *tab;
1733     char      saved_legend[FMT_LEG_LEN + 5];
1735     if(calc_width){
1736         legendwidth = 0;
1737     }
1738     else{
1739         legendwidth = im->legendwidth - 2 * border;
1740     }
1743     if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1744         if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1745             rrd_set_error("malloc for legspace");
1746             return -1;
1747         }
1749         for (i = 0; i < im->gdes_c; i++) {
1750             char      prt_fctn; /*special printfunctions */
1751             if(calc_width){
1752                 strcpy(saved_legend, im->gdes[i].legend);
1753             }
1755             fill_last = fill;
1756             /* hide legends for rules which are not displayed */
1757             if (im->gdes[i].gf == GF_TEXTALIGN) {
1758                 default_txtalign = im->gdes[i].txtalign;
1759             }
1761             if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1762                 if (im->gdes[i].gf == GF_HRULE
1763                     && (im->gdes[i].yrule <
1764                         im->minval || im->gdes[i].yrule > im->maxval))
1765                     im->gdes[i].legend[0] = '\0';
1766                 if (im->gdes[i].gf == GF_VRULE
1767                     && (im->gdes[i].xrule <
1768                         im->start || im->gdes[i].xrule > im->end))
1769                     im->gdes[i].legend[0] = '\0';
1770             }
1772             /* turn \\t into tab */
1773             while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1774                 memmove(tab, tab + 1, strlen(tab));
1775                 tab[0] = (char) 9;
1776             }
1778             leg_cc = strlen(im->gdes[i].legend);
1779             /* is there a controle code at the end of the legend string ? */
1780             if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1781                 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1782                 leg_cc -= 2;
1783                 im->gdes[i].legend[leg_cc] = '\0';
1784             } else {
1785                 prt_fctn = '\0';
1786             }
1787             /* only valid control codes */
1788             if (prt_fctn != 'l' && prt_fctn != 'n' &&   /* a synonym for l */
1789                 prt_fctn != 'r' &&
1790                 prt_fctn != 'j' &&
1791                 prt_fctn != 'c' &&
1792                 prt_fctn != 'u' &&
1793                 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1794                 free(legspace);
1795                 rrd_set_error
1796                     ("Unknown control code at the end of '%s\\%c'",
1797                      im->gdes[i].legend, prt_fctn);
1798                 return -1;
1799             }
1800             /* \n -> \l */
1801             if (prt_fctn == 'n') {
1802                 prt_fctn = 'l';
1803             }
1805             /* remove exess space from the end of the legend for \g */
1806             while (prt_fctn == 'g' &&
1807                    leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1808                 leg_cc--;
1809                 im->gdes[i].legend[leg_cc] = '\0';
1810             }
1812             if (leg_cc != 0) {
1814                 /* no interleg space if string ends in \g */
1815                 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1816                 if (fill > 0) {
1817                     fill += legspace[i];
1818                 }
1819                 fill +=
1820                     gfx_get_text_width(im,
1821                                        fill + border,
1822                                        im->
1823                                        text_prop
1824                                        [TEXT_PROP_LEGEND].
1825                                        font_desc,
1826                                        im->tabwidth, im->gdes[i].legend);
1827                 leg_c++;
1828             } else {
1829                 legspace[i] = 0;
1830             }
1831             /* who said there was a special tag ... ? */
1832             if (prt_fctn == 'g') {
1833                 prt_fctn = '\0';
1834             }
1836             if (prt_fctn == '\0') {
1837                 if(calc_width && (fill > legendwidth)){
1838                     legendwidth = fill;
1839                 }
1840                 if (i == im->gdes_c - 1 || fill > legendwidth) {
1841                     /* just one legend item is left right or center */
1842                     switch (default_txtalign) {
1843                     case TXA_RIGHT:
1844                         prt_fctn = 'r';
1845                         break;
1846                     case TXA_CENTER:
1847                         prt_fctn = 'c';
1848                         break;
1849                     case TXA_JUSTIFIED:
1850                         prt_fctn = 'j';
1851                         break;
1852                     default:
1853                         prt_fctn = 'l';
1854                         break;
1855                     }
1856                 }
1857                 /* is it time to place the legends ? */
1858                 if (fill > legendwidth) {
1859                     if (leg_c > 1) {
1860                         /* go back one */
1861                         i--;
1862                         fill = fill_last;
1863                         leg_c--;
1864                     }
1865                 }
1866                 if (leg_c == 1 && prt_fctn == 'j') {
1867                     prt_fctn = 'l';
1868                 }
1869             }
1871             if (prt_fctn != '\0') {
1872                 leg_x = border;
1873                 if (leg_c >= 2 && prt_fctn == 'j') {
1874                     glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1875                 } else {
1876                     glue = 0;
1877                 }
1878                 if (prt_fctn == 'c')
1879                     leg_x = border + (double)(legendwidth - fill) / 2.0;
1880                 if (prt_fctn == 'r')
1881                     leg_x = legendwidth - fill + border;
1882                 for (ii = mark; ii <= i; ii++) {
1883                     if (im->gdes[ii].legend[0] == '\0')
1884                         continue;   /* skip empty legends */
1885                     im->gdes[ii].leg_x = leg_x;
1886                     im->gdes[ii].leg_y = leg_y + border;
1887                     leg_x +=
1888                         (double)gfx_get_text_width(im, leg_x,
1889                                            im->
1890                                            text_prop
1891                                            [TEXT_PROP_LEGEND].
1892                                            font_desc,
1893                                            im->tabwidth, im->gdes[ii].legend)
1894                         +(double)legspace[ii]
1895                         + glue;
1896                 }
1897                 leg_y_prev = leg_y;
1898                 if (leg_x > border || prt_fctn == 's')
1899                     leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1900                 if (prt_fctn == 's')
1901                     leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1902                 if (prt_fctn == 'u')
1903                     leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1905                 if(calc_width && (fill > legendwidth)){
1906                     legendwidth = fill;
1907                 }
1908                 fill = 0;
1909                 leg_c = 0;
1910                 mark = ii;
1911             }
1913             if(calc_width){
1914                 strcpy(im->gdes[i].legend, saved_legend);
1915             }
1916         }
1918         if(calc_width){
1919             im->legendwidth = legendwidth + 2 * border;
1920         }
1921         else{
1922             im->legendheight = leg_y + border * 0.6;
1923         }
1924         free(legspace);
1925     }
1926     return 0;
1929 /* create a grid on the graph. it determines what to do
1930    from the values of xsize, start and end */
1932 /* the xaxis labels are determined from the number of seconds per pixel
1933    in the requested graph */
1935 int calc_horizontal_grid(
1936     image_desc_t
1937     *im)
1939     double    range;
1940     double    scaledrange;
1941     int       pixel, i;
1942     int       gridind = 0;
1943     int       decimals, fractionals;
1945     im->ygrid_scale.labfact = 2;
1946     range = im->maxval - im->minval;
1947     scaledrange = range / im->magfact;
1948     /* does the scale of this graph make it impossible to put lines
1949        on it? If so, give up. */
1950     if (isnan(scaledrange)) {
1951         return 0;
1952     }
1954     /* find grid spaceing */
1955     pixel = 1;
1956     if (isnan(im->ygridstep)) {
1957         if (im->extra_flags & ALTYGRID) {
1958             /* find the value with max number of digits. Get number of digits */
1959             decimals =
1960                 ceil(log10
1961                      (max(fabs(im->maxval), fabs(im->minval)) *
1962                       im->viewfactor / im->magfact));
1963             if (decimals <= 0)  /* everything is small. make place for zero */
1964                 decimals = 1;
1965             im->ygrid_scale.gridstep =
1966                 pow((double) 10,
1967                     floor(log10(range * im->viewfactor / im->magfact))) /
1968                 im->viewfactor * im->magfact;
1969             if (im->ygrid_scale.gridstep == 0)  /* range is one -> 0.1 is reasonable scale */
1970                 im->ygrid_scale.gridstep = 0.1;
1971             /* should have at least 5 lines but no more then 15 */
1972             if (range / im->ygrid_scale.gridstep < 5
1973                 && im->ygrid_scale.gridstep >= 30)
1974                 im->ygrid_scale.gridstep /= 10;
1975             if (range / im->ygrid_scale.gridstep > 15)
1976                 im->ygrid_scale.gridstep *= 10;
1977             if (range / im->ygrid_scale.gridstep > 5) {
1978                 im->ygrid_scale.labfact = 1;
1979                 if (range / im->ygrid_scale.gridstep > 8
1980                     || im->ygrid_scale.gridstep <
1981                     1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1982                     im->ygrid_scale.labfact = 2;
1983             } else {
1984                 im->ygrid_scale.gridstep /= 5;
1985                 im->ygrid_scale.labfact = 5;
1986             }
1987             fractionals =
1988                 floor(log10
1989                       (im->ygrid_scale.gridstep *
1990                        (double) im->ygrid_scale.labfact * im->viewfactor /
1991                        im->magfact));
1992             if (fractionals < 0) {  /* small amplitude. */
1993                 int       len = decimals - fractionals + 1;
1995                 if (im->unitslength < len + 2)
1996                     im->unitslength = len + 2;
1997                 sprintf(im->ygrid_scale.labfmt,
1998                         "%%%d.%df%s", len,
1999                         -fractionals, (im->symbol != ' ' ? " %c" : ""));
2000             } else {
2001                 int       len = decimals + 1;
2003                 if (im->unitslength < len + 2)
2004                     im->unitslength = len + 2;
2005                 sprintf(im->ygrid_scale.labfmt,
2006                         "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
2007             }
2008         } else {        /* classic rrd grid */
2009             for (i = 0; ylab[i].grid > 0; i++) {
2010                 pixel = im->ysize / (scaledrange / ylab[i].grid);
2011                 gridind = i;
2012                 if (pixel >= 5)
2013                     break;
2014             }
2016             for (i = 0; i < 4; i++) {
2017                 if (pixel * ylab[gridind].lfac[i] >=
2018                     1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
2019                     im->ygrid_scale.labfact = ylab[gridind].lfac[i];
2020                     break;
2021                 }
2022             }
2024             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
2025         }
2026     } else {
2027         im->ygrid_scale.gridstep = im->ygridstep;
2028         im->ygrid_scale.labfact = im->ylabfact;
2029     }
2030     return 1;
2033 int draw_horizontal_grid(
2034     image_desc_t
2035     *im)
2037     int       i;
2038     double    scaledstep;
2039     char      graph_label[100];
2040     int       nlabels = 0;
2041     double    X0 = im->xorigin;
2042     double    X1 = im->xorigin + im->xsize;
2043     int       sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2044     int       egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2045     double    MaxY;
2046     double second_axis_magfact = 0;
2047     char *second_axis_symb = "";
2049     scaledstep =
2050         im->ygrid_scale.gridstep /
2051         (double) im->magfact * (double) im->viewfactor;
2052     MaxY = scaledstep * (double) egrid;
2053     for (i = sgrid; i <= egrid; i++) {
2054         double    Y0 = ytr(im,
2055                            im->ygrid_scale.gridstep * i);
2056         double    YN = ytr(im,
2057                            im->ygrid_scale.gridstep * (i + 1));
2059         if (floor(Y0 + 0.5) >=
2060             im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2061             /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2062                with the chosen settings. Add a label if required by settings, or if
2063                there is only one label so far and the next grid line is out of bounds. */
2064             if (i % im->ygrid_scale.labfact == 0
2065                 || (nlabels == 1
2066                     && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2067                 if (im->symbol == ' ') {
2068                     if (im->extra_flags & ALTYGRID) {
2069                         sprintf(graph_label,
2070                                 im->ygrid_scale.labfmt,
2071                                 scaledstep * (double) i);
2072                     } else {
2073                         if (MaxY < 10) {
2074                             sprintf(graph_label, "%4.1f",
2075                                     scaledstep * (double) i);
2076                         } else {
2077                             sprintf(graph_label, "%4.0f",
2078                                     scaledstep * (double) i);
2079                         }
2080                     }
2081                 } else {
2082                     char      sisym = (i == 0 ? ' ' : im->symbol);
2084                     if (im->extra_flags & ALTYGRID) {
2085                         sprintf(graph_label,
2086                                 im->ygrid_scale.labfmt,
2087                                 scaledstep * (double) i, sisym);
2088                     } else {
2089                         if (MaxY < 10) {
2090                             sprintf(graph_label, "%4.1f %c",
2091                                     scaledstep * (double) i, sisym);
2092                         } else {
2093                             sprintf(graph_label, "%4.0f %c",
2094                                     scaledstep * (double) i, sisym);
2095                         }
2096                     }
2097                 }
2098                 nlabels++;
2099                 if (im->second_axis_scale != 0){
2100                         char graph_label_right[100];
2101                         double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2102                         if (im->second_axis_format[0] == '\0'){
2103                             if (!second_axis_magfact){
2104                                 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2105                                 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2106                             }
2107                             sval /= second_axis_magfact;
2109                             if(MaxY < 10) {
2110                                 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2111                             } else {
2112                                 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2113                             }
2114                         }
2115                         else {
2116                            sprintf(graph_label_right,im->second_axis_format,sval);
2117                         }
2118                         gfx_text ( im,
2119                                X1+7, Y0,
2120                                im->graph_col[GRC_FONT],
2121                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2122                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2123                                graph_label_right );
2124                 }
2126                 gfx_text(im,
2127                          X0 -
2128                          im->
2129                          text_prop[TEXT_PROP_AXIS].
2130                          size, Y0,
2131                          im->graph_col[GRC_FONT],
2132                          im->
2133                          text_prop[TEXT_PROP_AXIS].
2134                          font_desc,
2135                          im->tabwidth, 0.0,
2136                          GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2137                 gfx_line(im, X0 - 2, Y0, X0, Y0,
2138                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2139                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2140                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2141                 gfx_dashed_line(im, X0 - 2, Y0,
2142                                 X1 + 2, Y0,
2143                                 MGRIDWIDTH,
2144                                 im->
2145                                 graph_col
2146                                 [GRC_MGRID],
2147                                 im->grid_dash_on, im->grid_dash_off);
2148             } else if (!(im->extra_flags & NOMINOR)) {
2149                 gfx_line(im,
2150                          X0 - 2, Y0,
2151                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2152                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2153                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2154                 gfx_dashed_line(im, X0 - 1, Y0,
2155                                 X1 + 1, Y0,
2156                                 GRIDWIDTH,
2157                                 im->
2158                                 graph_col[GRC_GRID],
2159                                 im->grid_dash_on, im->grid_dash_off);
2160             }
2161         }
2162     }
2163     return 1;
2166 /* this is frexp for base 10 */
2167 double    frexp10(
2168     double,
2169     double *);
2170 double frexp10(
2171     double x,
2172     double *e)
2174     double    mnt;
2175     int       iexp;
2177     iexp = floor(log((double)fabs(x)) / log((double)10));
2178     mnt = x / pow(10.0, iexp);
2179     if (mnt >= 10.0) {
2180         iexp++;
2181         mnt = x / pow(10.0, iexp);
2182     }
2183     *e = iexp;
2184     return mnt;
2188 /* logaritmic horizontal grid */
2189 int horizontal_log_grid(
2190     image_desc_t
2191     *im)
2193     double    yloglab[][10] = {
2194         {
2195          1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2196          0.0, 0.0, 0.0}, {
2197                           1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2198                           0.0, 0.0, 0.0}, {
2199                                            1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2200                                            0.0, 0.0, 0.0}, {
2201                                                             1.0, 2.0, 4.0,
2202                                                             6.0, 8.0, 10.,
2203                                                             0.0,
2204                                                             0.0, 0.0, 0.0}, {
2205                                                                              1.0,
2206                                                                              2.0,
2207                                                                              3.0,
2208                                                                              4.0,
2209                                                                              5.0,
2210                                                                              6.0,
2211                                                                              7.0,
2212                                                                              8.0,
2213                                                                              9.0,
2214                                                                              10.},
2215         {
2216          0, 0, 0, 0, 0, 0, 0, 0, 0, 0}  /* last line */
2217     };
2218     int       i, j, val_exp, min_exp;
2219     double    nex;      /* number of decades in data */
2220     double    logscale; /* scale in logarithmic space */
2221     int       exfrac = 1;   /* decade spacing */
2222     int       mid = -1; /* row in yloglab for major grid */
2223     double    mspac;    /* smallest major grid spacing (pixels) */
2224     int       flab;     /* first value in yloglab to use */
2225     double    value, tmp, pre_value;
2226     double    X0, X1, Y0;
2227     char      graph_label[100];
2229     nex = log10(im->maxval / im->minval);
2230     logscale = im->ysize / nex;
2231     /* major spacing for data with high dynamic range */
2232     while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2233         if (exfrac == 1)
2234             exfrac = 3;
2235         else
2236             exfrac += 3;
2237     }
2239     /* major spacing for less dynamic data */
2240     do {
2241         /* search best row in yloglab */
2242         mid++;
2243         for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2244         mspac = logscale * log10(10.0 / yloglab[mid][i]);
2245     }
2246     while (mspac >
2247            2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2248     if (mid)
2249         mid--;
2250     /* find first value in yloglab */
2251     for (flab = 0;
2252          yloglab[mid][flab] < 10
2253          && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2254     if (yloglab[mid][flab] == 10.0) {
2255         tmp += 1.0;
2256         flab = 0;
2257     }
2258     val_exp = tmp;
2259     if (val_exp % exfrac)
2260         val_exp += abs(-val_exp % exfrac);
2261     X0 = im->xorigin;
2262     X1 = im->xorigin + im->xsize;
2263     /* draw grid */
2264     pre_value = DNAN;
2265     while (1) {
2267         value = yloglab[mid][flab] * pow(10.0, val_exp);
2268         if (AlmostEqual2sComplement(value, pre_value, 4))
2269             break;      /* it seems we are not converging */
2270         pre_value = value;
2271         Y0 = ytr(im, value);
2272         if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2273             break;
2274         /* major grid line */
2275         gfx_line(im,
2276                  X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2277         gfx_line(im, X1, Y0, X1 + 2, Y0,
2278                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2279         gfx_dashed_line(im, X0 - 2, Y0,
2280                         X1 + 2, Y0,
2281                         MGRIDWIDTH,
2282                         im->
2283                         graph_col
2284                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2285         /* label */
2286         if (im->extra_flags & FORCE_UNITS_SI) {
2287             int       scale;
2288             double    pvalue;
2289             char      symbol;
2291             scale = floor(val_exp / 3.0);
2292             if (value >= 1.0)
2293                 pvalue = pow(10.0, val_exp % 3);
2294             else
2295                 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2296             pvalue *= yloglab[mid][flab];
2297             if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2298                 && ((scale + si_symbcenter) >= 0))
2299                 symbol = si_symbol[scale + si_symbcenter];
2300             else
2301                 symbol = '?';
2302             sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2303         } else {
2304             sprintf(graph_label, "%3.0e", value);
2305         }
2306         if (im->second_axis_scale != 0){
2307                 char graph_label_right[100];
2308                 double sval = value*im->second_axis_scale+im->second_axis_shift;
2309                 if (im->second_axis_format[0] == '\0'){
2310                         if (im->extra_flags & FORCE_UNITS_SI) {
2311                                 double mfac = 1;
2312                                 char   *symb = "";
2313                                 auto_scale(im,&sval,&symb,&mfac);
2314                                 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2315                         }
2316                         else {
2317                                 sprintf(graph_label_right,"%3.0e", sval);
2318                         }
2319                 }
2320                 else {
2321                       sprintf(graph_label_right,im->second_axis_format,sval,"");
2322                 }
2324                 gfx_text ( im,
2325                                X1+7, Y0,
2326                                im->graph_col[GRC_FONT],
2327                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2328                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2329                                graph_label_right );
2330         }
2332         gfx_text(im,
2333                  X0 -
2334                  im->
2335                  text_prop[TEXT_PROP_AXIS].
2336                  size, Y0,
2337                  im->graph_col[GRC_FONT],
2338                  im->
2339                  text_prop[TEXT_PROP_AXIS].
2340                  font_desc,
2341                  im->tabwidth, 0.0,
2342                  GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2343         /* minor grid */
2344         if (mid < 4 && exfrac == 1) {
2345             /* find first and last minor line behind current major line
2346              * i is the first line and j tha last */
2347             if (flab == 0) {
2348                 min_exp = val_exp - 1;
2349                 for (i = 1; yloglab[mid][i] < 10.0; i++);
2350                 i = yloglab[mid][i - 1] + 1;
2351                 j = 10;
2352             } else {
2353                 min_exp = val_exp;
2354                 i = yloglab[mid][flab - 1] + 1;
2355                 j = yloglab[mid][flab];
2356             }
2358             /* draw minor lines below current major line */
2359             for (; i < j; i++) {
2361                 value = i * pow(10.0, min_exp);
2362                 if (value < im->minval)
2363                     continue;
2364                 Y0 = ytr(im, value);
2365                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2366                     break;
2367                 /* draw lines */
2368                 gfx_line(im,
2369                          X0 - 2, Y0,
2370                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2371                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2372                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2373                 gfx_dashed_line(im, X0 - 1, Y0,
2374                                 X1 + 1, Y0,
2375                                 GRIDWIDTH,
2376                                 im->
2377                                 graph_col[GRC_GRID],
2378                                 im->grid_dash_on, im->grid_dash_off);
2379             }
2380         } else if (exfrac > 1) {
2381             for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2382                 value = pow(10.0, i);
2383                 if (value < im->minval)
2384                     continue;
2385                 Y0 = ytr(im, value);
2386                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2387                     break;
2388                 /* draw lines */
2389                 gfx_line(im,
2390                          X0 - 2, Y0,
2391                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2392                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2393                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2394                 gfx_dashed_line(im, X0 - 1, Y0,
2395                                 X1 + 1, Y0,
2396                                 GRIDWIDTH,
2397                                 im->
2398                                 graph_col[GRC_GRID],
2399                                 im->grid_dash_on, im->grid_dash_off);
2400             }
2401         }
2403         /* next decade */
2404         if (yloglab[mid][++flab] == 10.0) {
2405             flab = 0;
2406             val_exp += exfrac;
2407         }
2408     }
2410     /* draw minor lines after highest major line */
2411     if (mid < 4 && exfrac == 1) {
2412         /* find first and last minor line below current major line
2413          * i is the first line and j tha last */
2414         if (flab == 0) {
2415             min_exp = val_exp - 1;
2416             for (i = 1; yloglab[mid][i] < 10.0; i++);
2417             i = yloglab[mid][i - 1] + 1;
2418             j = 10;
2419         } else {
2420             min_exp = val_exp;
2421             i = yloglab[mid][flab - 1] + 1;
2422             j = yloglab[mid][flab];
2423         }
2425         /* draw minor lines below current major line */
2426         for (; i < j; i++) {
2428             value = i * pow(10.0, min_exp);
2429             if (value < im->minval)
2430                 continue;
2431             Y0 = ytr(im, value);
2432             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2433                 break;
2434             /* draw lines */
2435             gfx_line(im,
2436                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2437             gfx_line(im, X1, Y0, X1 + 2, Y0,
2438                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2439             gfx_dashed_line(im, X0 - 1, Y0,
2440                             X1 + 1, Y0,
2441                             GRIDWIDTH,
2442                             im->
2443                             graph_col[GRC_GRID],
2444                             im->grid_dash_on, im->grid_dash_off);
2445         }
2446     }
2447     /* fancy minor gridlines */
2448     else if (exfrac > 1) {
2449         for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2450             value = pow(10.0, i);
2451             if (value < im->minval)
2452                 continue;
2453             Y0 = ytr(im, value);
2454             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2455                 break;
2456             /* draw lines */
2457             gfx_line(im,
2458                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2459             gfx_line(im, X1, Y0, X1 + 2, Y0,
2460                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2461             gfx_dashed_line(im, X0 - 1, Y0,
2462                             X1 + 1, Y0,
2463                             GRIDWIDTH,
2464                             im->
2465                             graph_col[GRC_GRID],
2466                             im->grid_dash_on, im->grid_dash_off);
2467         }
2468     }
2470     return 1;
2474 void vertical_grid(
2475     image_desc_t *im)
2477     int       xlab_sel; /* which sort of label and grid ? */
2478     time_t    ti, tilab, timajor;
2479     long      factor;
2480     char      graph_label[100];
2481     double    X0, Y0, Y1;   /* points for filled graph and more */
2482     struct tm tm;
2484     /* the type of time grid is determined by finding
2485        the number of seconds per pixel in the graph */
2486     if (im->xlab_user.minsec == -1) {
2487         factor = (im->end - im->start) / im->xsize;
2488         xlab_sel = 0;
2489         while (xlab[xlab_sel + 1].minsec !=
2490                -1 && xlab[xlab_sel + 1].minsec <= factor) {
2491             xlab_sel++;
2492         }               /* pick the last one */
2493         while (xlab[xlab_sel - 1].minsec ==
2494                xlab[xlab_sel].minsec
2495                && xlab[xlab_sel].length > (im->end - im->start)) {
2496             xlab_sel--;
2497         }               /* go back to the smallest size */
2498         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2499         im->xlab_user.gridst = xlab[xlab_sel].gridst;
2500         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2501         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2502         im->xlab_user.labtm = xlab[xlab_sel].labtm;
2503         im->xlab_user.labst = xlab[xlab_sel].labst;
2504         im->xlab_user.precis = xlab[xlab_sel].precis;
2505         im->xlab_user.stst = xlab[xlab_sel].stst;
2506     }
2508     /* y coords are the same for every line ... */
2509     Y0 = im->yorigin;
2510     Y1 = im->yorigin - im->ysize;
2511     /* paint the minor grid */
2512     if (!(im->extra_flags & NOMINOR)) {
2513         for (ti = find_first_time(im->start,
2514                                   im->
2515                                   xlab_user.
2516                                   gridtm,
2517                                   im->
2518                                   xlab_user.
2519                                   gridst),
2520              timajor =
2521              find_first_time(im->start,
2522                              im->xlab_user.
2523                              mgridtm,
2524                              im->xlab_user.
2525                              mgridst);
2526              ti < im->end && ti != -1;
2527              ti =
2528              find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2529             ) {
2530             /* are we inside the graph ? */
2531             if (ti < im->start || ti > im->end)
2532                 continue;
2533             while (timajor < ti && timajor != -1) {
2534                 timajor = find_next_time(timajor,
2535                                          im->
2536                                          xlab_user.
2537                                          mgridtm, im->xlab_user.mgridst);
2538             }
2539             if (timajor == -1) break; /* fail in case of problems with time increments */
2540             if (ti == timajor)
2541                 continue;   /* skip as falls on major grid line */
2542             X0 = xtr(im, ti);
2543             gfx_line(im, X0, Y1 - 2, X0, Y1,
2544                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2545             gfx_line(im, X0, Y0, X0, Y0 + 2,
2546                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2547             gfx_dashed_line(im, X0, Y0 + 1, X0,
2548                             Y1 - 1, GRIDWIDTH,
2549                             im->
2550                             graph_col[GRC_GRID],
2551                             im->grid_dash_on, im->grid_dash_off);
2552         }
2553     }
2555     /* paint the major grid */
2556     for (ti = find_first_time(im->start,
2557                               im->
2558                               xlab_user.
2559                               mgridtm,
2560                               im->
2561                               xlab_user.
2562                               mgridst);
2563          ti < im->end && ti != -1;
2564          ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2565         ) {
2566         /* are we inside the graph ? */
2567         if (ti < im->start || ti > im->end)
2568             continue;
2569         X0 = xtr(im, ti);
2570         gfx_line(im, X0, Y1 - 2, X0, Y1,
2571                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2572         gfx_line(im, X0, Y0, X0, Y0 + 3,
2573                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2574         gfx_dashed_line(im, X0, Y0 + 3, X0,
2575                         Y1 - 2, MGRIDWIDTH,
2576                         im->
2577                         graph_col
2578                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2579     }
2580     /* paint the labels below the graph */
2581     for (ti =
2582          find_first_time(im->start -
2583                          im->xlab_user.
2584                          precis / 2,
2585                          im->xlab_user.
2586                          labtm,
2587                          im->xlab_user.
2588                          labst);
2589          (ti <=
2590          im->end -
2591          im->xlab_user.precis / 2) && ti != -1;
2592          ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2593         ) {
2594         tilab = ti + im->xlab_user.precis / 2;  /* correct time for the label */
2595         /* are we inside the graph ? */
2596         if (tilab < im->start || tilab > im->end)
2597             continue;
2598 #if HAVE_STRFTIME
2599         localtime_r(&tilab, &tm);
2600         strftime(graph_label, 99, im->xlab_user.stst, &tm);
2601 #else
2602 # error "your libc has no strftime I guess we'll abort the exercise here."
2603 #endif
2604         gfx_text(im,
2605                  xtr(im, tilab),
2606                  Y0 + 3,
2607                  im->graph_col[GRC_FONT],
2608                  im->
2609                  text_prop[TEXT_PROP_AXIS].
2610                  font_desc,
2611                  im->tabwidth, 0.0,
2612                  GFX_H_CENTER, GFX_V_TOP, graph_label);
2613     }
2618 void axis_paint(
2619     image_desc_t *im)
2621     /* draw x and y axis */
2622     /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2623        im->xorigin+im->xsize,im->yorigin-im->ysize,
2624        GRIDWIDTH, im->graph_col[GRC_AXIS]);
2626        gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2627        im->xorigin+im->xsize,im->yorigin-im->ysize,
2628        GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2630     gfx_line(im, im->xorigin - 4,
2631              im->yorigin,
2632              im->xorigin + im->xsize +
2633              4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2634     gfx_line(im, im->xorigin,
2635              im->yorigin + 4,
2636              im->xorigin,
2637              im->yorigin - im->ysize -
2638              4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2639     /* arrow for X and Y axis direction */
2640     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 */
2641                  im->graph_col[GRC_ARROW]);
2642     gfx_close_path(im);
2643     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 */
2644                  im->graph_col[GRC_ARROW]);
2645     gfx_close_path(im);
2646     if (im->second_axis_scale != 0){
2647        gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2648                          im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2649                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2650        gfx_new_area ( im,
2651                    im->xorigin+im->xsize-2,  im->yorigin-im->ysize-2,
2652                    im->xorigin+im->xsize+3,  im->yorigin-im->ysize-2,
2653                    im->xorigin+im->xsize,    im->yorigin-im->ysize-7, /* LINEOFFSET */
2654                    im->graph_col[GRC_ARROW]);
2655        gfx_close_path(im);
2656     }
2660 void grid_paint(
2661     image_desc_t *im)
2663     long      i;
2664     int       res = 0;
2665     double    X0, Y0;   /* points for filled graph and more */
2666     struct gfx_color_t water_color;
2668     if (im->draw_3d_border > 0) {
2669             /* draw 3d border */
2670             i = im->draw_3d_border;
2671             gfx_new_area(im, 0, im->yimg,
2672                          i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2673             gfx_add_point(im, im->ximg - i, i);
2674             gfx_add_point(im, im->ximg, 0);
2675             gfx_add_point(im, 0, 0);
2676             gfx_close_path(im);
2677             gfx_new_area(im, i, im->yimg - i,
2678                          im->ximg - i,
2679                          im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2680             gfx_add_point(im, im->ximg, 0);
2681             gfx_add_point(im, im->ximg, im->yimg);
2682             gfx_add_point(im, 0, im->yimg);
2683             gfx_close_path(im);
2684     }
2685     if (im->draw_x_grid == 1)
2686         vertical_grid(im);
2687     if (im->draw_y_grid == 1) {
2688         if (im->logarithmic) {
2689             res = horizontal_log_grid(im);
2690         } else {
2691             res = draw_horizontal_grid(im);
2692         }
2694         /* dont draw horizontal grid if there is no min and max val */
2695         if (!res) {
2696             char     *nodata = "No Data found";
2698             gfx_text(im, im->ximg / 2,
2699                      (2 * im->yorigin -
2700                       im->ysize) / 2,
2701                      im->graph_col[GRC_FONT],
2702                      im->
2703                      text_prop[TEXT_PROP_AXIS].
2704                      font_desc,
2705                      im->tabwidth, 0.0,
2706                      GFX_H_CENTER, GFX_V_CENTER, nodata);
2707         }
2708     }
2710     /* yaxis unit description */
2711     if (im->ylegend[0] != '\0'){
2712         gfx_text(im,
2713                  im->xOriginLegendY+10,
2714                  im->yOriginLegendY,
2715                  im->graph_col[GRC_FONT],
2716                  im->
2717                  text_prop[TEXT_PROP_UNIT].
2718                  font_desc,
2719                  im->tabwidth,
2720                  RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2722     }
2723     if (im->second_axis_legend[0] != '\0'){
2724             gfx_text( im,
2725                   im->xOriginLegendY2+10,
2726                   im->yOriginLegendY2,
2727                   im->graph_col[GRC_FONT],
2728                   im->text_prop[TEXT_PROP_UNIT].font_desc,
2729                   im->tabwidth,
2730                   RRDGRAPH_YLEGEND_ANGLE,
2731                   GFX_H_CENTER, GFX_V_CENTER,
2732                   im->second_axis_legend);
2733     }
2735     /* graph title */
2736     gfx_text(im,
2737              im->xOriginTitle, im->yOriginTitle+6,
2738              im->graph_col[GRC_FONT],
2739              im->
2740              text_prop[TEXT_PROP_TITLE].
2741              font_desc,
2742              im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2743     /* rrdtool 'logo' */
2744     if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2745         water_color = im->graph_col[GRC_FONT];
2746         water_color.alpha = 0.3;
2747         double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2748         gfx_text(im, xpos, 5,
2749                  water_color,
2750                  im->
2751                  text_prop[TEXT_PROP_WATERMARK].
2752                  font_desc, im->tabwidth,
2753                  -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2754     }
2755     /* graph watermark */
2756     if (im->watermark[0] != '\0') {
2757         water_color = im->graph_col[GRC_FONT];
2758         water_color.alpha = 0.3;
2759         gfx_text(im,
2760                  im->ximg / 2, im->yimg - 6,
2761                  water_color,
2762                  im->
2763                  text_prop[TEXT_PROP_WATERMARK].
2764                  font_desc, im->tabwidth, 0,
2765                  GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2766     }
2768     /* graph labels */
2769     if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2770         for (i = 0; i < im->gdes_c; i++) {
2771             if (im->gdes[i].legend[0] == '\0')
2772                 continue;
2773             /* im->gdes[i].leg_y is the bottom of the legend */
2774             X0 = im->xOriginLegend + im->gdes[i].leg_x;
2775             Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2776             gfx_text(im, X0, Y0,
2777                      im->graph_col[GRC_FONT],
2778                      im->
2779                      text_prop
2780                      [TEXT_PROP_LEGEND].font_desc,
2781                      im->tabwidth, 0.0,
2782                      GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2783             /* The legend for GRAPH items starts with "M " to have
2784                enough space for the box */
2785             if (im->gdes[i].gf != GF_PRINT &&
2786                 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2787                 double    boxH, boxV;
2788                 double    X1, Y1;
2790                 boxH = gfx_get_text_width(im, 0,
2791                                           im->
2792                                           text_prop
2793                                           [TEXT_PROP_LEGEND].
2794                                           font_desc,
2795                                           im->tabwidth, "o") * 1.2;
2796                 boxV = boxH;
2797                 /* shift the box up a bit */
2798                 Y0 -= boxV * 0.4;
2800         if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */ 
2801                         cairo_save(im->cr);
2802                         cairo_new_path(im->cr);
2803                         cairo_set_line_width(im->cr, 1.0);
2804                         gfx_line(im,
2805                                 X0, Y0 - boxV / 2,
2806                                 X0 + boxH, Y0 - boxV / 2,
2807                                 1.0, im->gdes[i].col);
2808                         gfx_close_path(im);
2809                 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2810                         cairo_save(im->cr);
2811                         cairo_new_path(im->cr);
2812                         cairo_set_line_width(im->cr, 1.0);
2813                         gfx_line(im,
2814                                 X0 + boxH / 2, Y0,
2815                                 X0 + boxH / 2, Y0 - boxV,
2816                                 1.0, im->gdes[i].col);
2817                         gfx_close_path(im);
2818                 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2819                         cairo_save(im->cr);
2820                         cairo_new_path(im->cr);
2821                         cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2822                         gfx_line(im,
2823                                 X0, Y0,
2824                                 X0 + boxH, Y0 - boxV,
2825                                 im->gdes[i].linewidth, im->gdes[i].col);
2826                         gfx_close_path(im);
2827                 } else {
2828                 /* make sure transparent colors show up the same way as in the graph */
2829                         gfx_new_area(im,
2830                                      X0, Y0 - boxV,
2831                                      X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2832                         gfx_add_point(im, X0 + boxH, Y0 - boxV);
2833                         gfx_close_path(im);
2834                         gfx_new_area(im, X0, Y0 - boxV, X0,
2835                                      Y0, X0 + boxH, Y0, im->gdes[i].col);
2836                         gfx_add_point(im, X0 + boxH, Y0 - boxV);
2837                         gfx_close_path(im);
2838                         cairo_save(im->cr);
2839                         cairo_new_path(im->cr);
2840                         cairo_set_line_width(im->cr, 1.0);
2841                         X1 = X0 + boxH;
2842                         Y1 = Y0 - boxV;
2843                         gfx_line_fit(im, &X0, &Y0);
2844                         gfx_line_fit(im, &X1, &Y1);
2845                         cairo_move_to(im->cr, X0, Y0);
2846                         cairo_line_to(im->cr, X1, Y0);
2847                         cairo_line_to(im->cr, X1, Y1);
2848                         cairo_line_to(im->cr, X0, Y1);
2849                         cairo_close_path(im->cr);
2850                         cairo_set_source_rgba(im->cr,
2851                                               im->graph_col[GRC_FRAME].red,
2852                                               im->graph_col[GRC_FRAME].green,
2853                                               im->graph_col[GRC_FRAME].blue,
2854                                               im->graph_col[GRC_FRAME].alpha);
2855                 }
2856                 if (im->gdes[i].dash) {
2857                     /* make box borders in legend dashed if the graph is dashed */
2858                     double    dashes[] = {
2859                         3.0
2860                     };
2861                     cairo_set_dash(im->cr, dashes, 1, 0.0);
2862                 }
2863                 cairo_stroke(im->cr);
2864                 cairo_restore(im->cr);
2865             }
2866         }
2867     }
2871 /*****************************************************
2872  * lazy check make sure we rely need to create this graph
2873  *****************************************************/
2875 int lazy_check(
2876     image_desc_t *im)
2878     FILE     *fd = NULL;
2879     int       size = 1;
2880     struct stat imgstat;
2882     if (im->lazy == 0)
2883         return 0;       /* no lazy option */
2884     if (strlen(im->graphfile) == 0)
2885         return 0;       /* inmemory option */
2886     if (stat(im->graphfile, &imgstat) != 0)
2887         return 0;       /* can't stat */
2888     /* one pixel in the existing graph is more then what we would
2889        change here ... */
2890     if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2891         return 0;
2892     if ((fd = fopen(im->graphfile, "rb")) == NULL)
2893         return 0;       /* the file does not exist */
2894     switch (im->imgformat) {
2895     case IF_PNG:
2896         size = PngSize(fd, &(im->ximg), &(im->yimg));
2897         break;
2898     default:
2899         size = 1;
2900     }
2901     fclose(fd);
2902     return size;
2906 int graph_size_location(
2907     image_desc_t
2908     *im,
2909     int elements)
2911     /* The actual size of the image to draw is determined from
2912      ** several sources.  The size given on the command line is
2913      ** the graph area but we need more as we have to draw labels
2914      ** and other things outside the graph area. If the option
2915      ** --full-size-mode is selected the size defines the total
2916      ** image size and the size available for the graph is
2917      ** calculated.
2918      */
2920     /** +---+-----------------------------------+
2921      ** | y |...............graph title.........|
2922      ** |   +---+-------------------------------+
2923      ** | a | y |                               |
2924      ** | x |   |                               |
2925      ** | i | a |                               |
2926      ** | s | x |       main graph area         |
2927      ** |   | i |                               |
2928      ** | t | s |                               |
2929      ** | i |   |                               |
2930      ** | t | l |                               |
2931      ** | l | b +-------------------------------+
2932      ** | e | l |       x axis labels           |
2933      ** +---+---+-------------------------------+
2934      ** |....................legends............|
2935      ** +---------------------------------------+
2936      ** |                   watermark           |
2937      ** +---------------------------------------+
2938      */
2940     int       Xvertical = 0, Xvertical2 = 0, Ytitle =
2941         0, Xylabel = 0, Xmain = 0, Ymain =
2942         0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2944     // no legends and no the shall be plotted it's easy
2945     if (im->extra_flags & ONLY_GRAPH) {
2946         im->xorigin = 0;
2947         im->ximg = im->xsize;
2948         im->yimg = im->ysize;
2949         im->yorigin = im->ysize;
2950         xtr(im, 0);
2951         ytr(im, DNAN);
2952         return 0;
2953     }
2955     if(im->watermark[0] != '\0') {
2956         Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2957     }
2959     // calculate the width of the left vertical legend
2960     if (im->ylegend[0] != '\0') {
2961         Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2962     }
2964     // calculate the width of the right vertical legend
2965     if (im->second_axis_legend[0] != '\0') {
2966         Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2967     }
2968     else{
2969         Xvertical2 = Xspacing;
2970     }
2972     if (im->title[0] != '\0') {
2973         /* The title is placed "inbetween" two text lines so it
2974          ** automatically has some vertical spacing.  The horizontal
2975          ** spacing is added here, on each side.
2976          */
2977         /* if necessary, reduce the font size of the title until it fits the image width */
2978         Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2979     }
2980     else{
2981         // we have no title; get a little clearing from the top
2982         Ytitle = Yspacing;
2983     }
2985     if (elements) {
2986         if (im->draw_x_grid) {
2987             // calculate the height of the horizontal labelling
2988             Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2989         }
2990         if (im->draw_y_grid || im->forceleftspace) {
2991             // calculate the width of the vertical labelling
2992             Xylabel =
2993                 gfx_get_text_width(im, 0,
2994                                    im->text_prop[TEXT_PROP_AXIS].font_desc,
2995                                    im->tabwidth, "0") * im->unitslength;
2996         }
2997     }
2999     // add some space to the labelling
3000     Xylabel += Xspacing;
3002     /* If the legend is printed besides the graph the width has to be
3003      ** calculated first. Placing the legend north or south of the
3004      ** graph requires the width calculation first, so the legend is
3005      ** skipped for the moment.
3006      */
3007     im->legendheight = 0;
3008     im->legendwidth = 0;
3009     if (!(im->extra_flags & NOLEGEND)) {
3010         if(im->legendposition == WEST || im->legendposition == EAST){
3011             if (leg_place(im, 1) == -1){
3012                 return -1;
3013             }
3014         }
3015     }
3017     if (im->extra_flags & FULL_SIZE_MODE) {
3019         /* The actual size of the image to draw has been determined by the user.
3020          ** The graph area is the space remaining after accounting for the legend,
3021          ** the watermark, the axis labels, and the title.
3022          */
3023         im->ximg = im->xsize;
3024         im->yimg = im->ysize;
3025         Xmain = im->ximg;
3026         Ymain = im->yimg;
3028         /* Now calculate the total size.  Insert some spacing where
3029            desired.  im->xorigin and im->yorigin need to correspond
3030            with the lower left corner of the main graph area or, if
3031            this one is not set, the imaginary box surrounding the
3032            pie chart area. */
3033         /* Initial size calculation for the main graph area */
3035         Xmain -= Xylabel;// + Xspacing;
3036         if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3037             Xmain -= im->legendwidth;// + Xspacing;
3038         }
3039         if (im->second_axis_scale != 0){
3040             Xmain -= Xylabel;
3041         }
3042         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3043             Xmain -= Xspacing;
3044         }
3046         Xmain -= Xvertical + Xvertical2;
3048         /* limit the remaining space to 0 */
3049         if(Xmain < 1){
3050             Xmain = 1;
3051         }
3052         im->xsize = Xmain;
3054         /* Putting the legend north or south, the height can now be calculated */
3055         if (!(im->extra_flags & NOLEGEND)) {
3056             if(im->legendposition == NORTH || im->legendposition == SOUTH){
3057                 im->legendwidth = im->ximg;
3058                 if (leg_place(im, 0) == -1){
3059                     return -1;
3060                 }
3061             }
3062         }
3064         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3065             Ymain -=  Yxlabel + im->legendheight;
3066         }
3067         else{
3068             Ymain -= Yxlabel;
3069         }
3071         /* reserve space for the title *or* some padding above the graph */
3072         Ymain -= Ytitle;
3074             /* reserve space for padding below the graph */
3075         if (im->extra_flags & NOLEGEND) {
3076             Ymain -= 0.5*Yspacing;
3077         }
3079         if (im->watermark[0] != '\0') {
3080             Ymain -= Ywatermark;
3081         }
3082         /* limit the remaining height to 0 */
3083         if(Ymain < 1){
3084             Ymain = 1;
3085         }
3086         im->ysize = Ymain;
3087     } else {            /* dimension options -width and -height refer to the dimensions of the main graph area */
3089         /* The actual size of the image to draw is determined from
3090          ** several sources.  The size given on the command line is
3091          ** the graph area but we need more as we have to draw labels
3092          ** and other things outside the graph area.
3093          */
3095         if (elements) {
3096             Xmain = im->xsize; // + Xspacing;
3097             Ymain = im->ysize;
3098         }
3100         im->ximg = Xmain + Xylabel;
3101         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3102             im->ximg += Xspacing;
3103         }
3105         if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3106             im->ximg += im->legendwidth;// + Xspacing;
3107         }
3108         if (im->second_axis_scale != 0){
3109             im->ximg += Xylabel;
3110         }
3112         im->ximg += Xvertical + Xvertical2;
3114         if (!(im->extra_flags & NOLEGEND)) {
3115             if(im->legendposition == NORTH || im->legendposition == SOUTH){
3116                 im->legendwidth = im->ximg;
3117                 if (leg_place(im, 0) == -1){
3118                     return -1;
3119                 }
3120             }
3121         }
3123         im->yimg = Ymain + Yxlabel;
3124         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3125              im->yimg += im->legendheight;
3126         }
3128         /* reserve space for the title *or* some padding above the graph */
3129         if (Ytitle) {
3130             im->yimg += Ytitle;
3131         } else {
3132             im->yimg += 1.5 * Yspacing;
3133         }
3134         /* reserve space for padding below the graph */
3135         if (im->extra_flags & NOLEGEND) {
3136             im->yimg += 0.5*Yspacing;
3137         }
3139         if (im->watermark[0] != '\0') {
3140             im->yimg += Ywatermark;
3141         }
3142     }
3145     /* In case of putting the legend in west or east position the first
3146      ** legend calculation might lead to wrong positions if some items
3147      ** are not aligned on the left hand side (e.g. centered) as the
3148      ** legendwidth wight have been increased after the item was placed.
3149      ** In this case the positions have to be recalculated.
3150      */
3151     if (!(im->extra_flags & NOLEGEND)) {
3152         if(im->legendposition == WEST || im->legendposition == EAST){
3153             if (leg_place(im, 0) == -1){
3154                 return -1;
3155             }
3156         }
3157     }
3159     /* After calculating all dimensions
3160      ** it is now possible to calculate
3161      ** all offsets.
3162      */
3163     switch(im->legendposition){
3164         case NORTH:
3165             im->xOriginTitle   = (im->ximg / 2);
3166             im->yOriginTitle   = 0;
3168             im->xOriginLegend  = 0;
3169             im->yOriginLegend  = Ytitle;
3171             im->xOriginLegendY = 0;
3172             im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3174             im->xorigin        = Xvertical + Xylabel;
3175             im->yorigin        = Ytitle + im->legendheight + Ymain;
3177             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3178             if (im->second_axis_scale != 0){
3179                 im->xOriginLegendY2 += Xylabel;
3180             }
3181             im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3183             break;
3185         case WEST:
3186             im->xOriginTitle   = im->legendwidth + im->xsize / 2;
3187             im->yOriginTitle   = 0;
3189             im->xOriginLegend  = 0;
3190             im->yOriginLegend  = Ytitle;
3192             im->xOriginLegendY = im->legendwidth;
3193             im->yOriginLegendY = Ytitle + (Ymain / 2);
3195             im->xorigin        = im->legendwidth + Xvertical + Xylabel;
3196             im->yorigin        = Ytitle + Ymain;
3198             im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3199             if (im->second_axis_scale != 0){
3200                 im->xOriginLegendY2 += Xylabel;
3201             }
3202             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3204             break;
3206         case SOUTH:
3207             im->xOriginTitle   = im->ximg / 2;
3208             im->yOriginTitle   = 0;
3210             im->xOriginLegend  = 0;
3211             im->yOriginLegend  = Ytitle + Ymain + Yxlabel;
3213             im->xOriginLegendY = 0;
3214             im->yOriginLegendY = Ytitle + (Ymain / 2);
3216             im->xorigin        = Xvertical + Xylabel;
3217             im->yorigin        = Ytitle + Ymain;
3219             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3220             if (im->second_axis_scale != 0){
3221                 im->xOriginLegendY2 += Xylabel;
3222             }
3223             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3225             break;
3227         case EAST:
3228             im->xOriginTitle   = im->xsize / 2;
3229             im->yOriginTitle   = 0;
3231             im->xOriginLegend  = Xvertical + Xylabel + Xmain + Xvertical2;
3232             if (im->second_axis_scale != 0){
3233                 im->xOriginLegend += Xylabel;
3234             }
3235             im->yOriginLegend  = Ytitle;
3237             im->xOriginLegendY = 0;
3238             im->yOriginLegendY = Ytitle + (Ymain / 2);
3240             im->xorigin        = Xvertical + Xylabel;
3241             im->yorigin        = Ytitle + Ymain;
3243             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3244             if (im->second_axis_scale != 0){
3245                 im->xOriginLegendY2 += Xylabel;
3246             }
3247             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3249             if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3250                 im->xOriginTitle    += Xspacing;
3251                 im->xOriginLegend   += Xspacing;
3252                 im->xOriginLegendY  += Xspacing;
3253                 im->xorigin         += Xspacing;
3254                 im->xOriginLegendY2 += Xspacing;
3255             }
3256             break;
3257     }
3259     xtr(im, 0);
3260     ytr(im, DNAN);
3261     return 0;
3264 static cairo_status_t cairo_output(
3265     void *closure,
3266     const unsigned char
3267     *data,
3268     unsigned int length)
3270     image_desc_t *im = (image_desc_t*)closure;
3272     im->rendered_image =
3273         (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3274     if (im->rendered_image == NULL)
3275         return CAIRO_STATUS_WRITE_ERROR;
3276     memcpy(im->rendered_image + im->rendered_image_size, data, length);
3277     im->rendered_image_size += length;
3278     return CAIRO_STATUS_SUCCESS;
3281 /* draw that picture thing ... */
3282 int graph_paint(
3283     image_desc_t *im)
3285     int       i, ii;
3286     int       lazy = lazy_check(im);
3287     double    areazero = 0.0;
3288     graph_desc_t *lastgdes = NULL;
3289     rrd_infoval_t info;
3291 //    PangoFontMap *font_map = pango_cairo_font_map_get_default();
3293     /* pull the data from the rrd files ... */
3294     if (data_fetch(im) == -1)
3295         return -1;
3296     /* evaluate VDEF and CDEF operations ... */
3297     if (data_calc(im) == -1)
3298         return -1;
3299     /* calculate and PRINT and GPRINT definitions. We have to do it at
3300      * this point because it will affect the length of the legends
3301      * if there are no graph elements (i==0) we stop here ...
3302      * if we are lazy, try to quit ...
3303      */
3304     i = print_calc(im);
3305     if (i < 0)
3306         return -1;
3308     /* if we want and can be lazy ... quit now */
3309     if (i == 0)
3310         return 0;
3312 /**************************************************************
3313  *** Calculating sizes and locations became a bit confusing ***
3314  *** so I moved this into a separate function.              ***
3315  **************************************************************/
3316     if (graph_size_location(im, i) == -1)
3317         return -1;
3319     info.u_cnt = im->xorigin;
3320     grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3321     info.u_cnt = im->yorigin - im->ysize;
3322     grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3323     info.u_cnt = im->xsize;
3324     grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3325     info.u_cnt = im->ysize;
3326     grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3327     info.u_cnt = im->ximg;
3328     grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3329     info.u_cnt = im->yimg;
3330     grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3331     info.u_cnt = im->start;
3332     grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3333     info.u_cnt = im->end;
3334     grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3336     /* if we want and can be lazy ... quit now */
3337     if (lazy)
3338         return 0;
3340     /* get actual drawing data and find min and max values */
3341     if (data_proc(im) == -1)
3342         return -1;
3343     if (!im->logarithmic) {
3344         si_unit(im);
3345     }
3347     /* identify si magnitude Kilo, Mega Giga ? */
3348     if (!im->rigid && !im->logarithmic)
3349         expand_range(im);   /* make sure the upper and lower limit are
3350                                sensible values */
3352     info.u_val = im->minval;
3353     grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3354     info.u_val = im->maxval;
3355     grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3358     if (!calc_horizontal_grid(im))
3359         return -1;
3360     /* reset precalc */
3361     ytr(im, DNAN);
3362 /*   if (im->gridfit)
3363      apply_gridfit(im); */
3364     /* the actual graph is created by going through the individual
3365        graph elements and then drawing them */
3366     cairo_surface_destroy(im->surface);
3367     switch (im->imgformat) {
3368     case IF_PNG:
3369         im->surface =
3370             cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3371                                        im->ximg * im->zoom,
3372                                        im->yimg * im->zoom);
3373         break;
3374     case IF_PDF:
3375         im->gridfit = 0;
3376         im->surface = strlen(im->graphfile)
3377             ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3378                                        im->yimg * im->zoom)
3379             : cairo_pdf_surface_create_for_stream
3380             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3381         break;
3382     case IF_EPS:
3383         im->gridfit = 0;
3384         im->surface = strlen(im->graphfile)
3385             ?
3386             cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3387                                     im->yimg * im->zoom)
3388             : cairo_ps_surface_create_for_stream
3389             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3390         break;
3391     case IF_SVG:
3392         im->gridfit = 0;
3393         im->surface = strlen(im->graphfile)
3394             ?
3395             cairo_svg_surface_create(im->
3396                                      graphfile,
3397                                      im->ximg * im->zoom, im->yimg * im->zoom)
3398             : cairo_svg_surface_create_for_stream
3399             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3400         cairo_svg_surface_restrict_to_version
3401             (im->surface, CAIRO_SVG_VERSION_1_1);
3402         break;
3403     };
3404     cairo_destroy(im->cr);
3405     im->cr = cairo_create(im->surface);
3406     cairo_set_antialias(im->cr, im->graph_antialias);
3407     cairo_scale(im->cr, im->zoom, im->zoom);
3408 //    pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3409     gfx_new_area(im, 0, 0, 0, im->yimg,
3410                  im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3411     gfx_add_point(im, im->ximg, 0);
3412     gfx_close_path(im);
3413     gfx_new_area(im, im->xorigin,
3414                  im->yorigin,
3415                  im->xorigin +
3416                  im->xsize, im->yorigin,
3417                  im->xorigin +
3418                  im->xsize,
3419                  im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3420     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3421     gfx_close_path(im);
3422     cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3423                     im->xsize, im->ysize + 2.0);
3424     cairo_clip(im->cr);
3425     if (im->minval > 0.0)
3426         areazero = im->minval;
3427     if (im->maxval < 0.0)
3428         areazero = im->maxval;
3429     for (i = 0; i < im->gdes_c; i++) {
3430         switch (im->gdes[i].gf) {
3431         case GF_CDEF:
3432         case GF_VDEF:
3433         case GF_DEF:
3434         case GF_PRINT:
3435         case GF_GPRINT:
3436         case GF_COMMENT:
3437         case GF_TEXTALIGN:
3438         case GF_HRULE:
3439         case GF_VRULE:
3440         case GF_XPORT:
3441         case GF_SHIFT:
3442             break;
3443         case GF_TICK:
3444             for (ii = 0; ii < im->xsize; ii++) {
3445                 if (!isnan(im->gdes[i].p_data[ii])
3446                     && im->gdes[i].p_data[ii] != 0.0) {
3447                     if (im->gdes[i].yrule > 0) {
3448                         gfx_line(im,
3449                                  im->xorigin + ii,
3450                                  im->yorigin + 1.0,
3451                                  im->xorigin + ii,
3452                                  im->yorigin -
3453                                  im->gdes[i].yrule *
3454                                  im->ysize, 1.0, im->gdes[i].col);
3455                     } else if (im->gdes[i].yrule < 0) {
3456                         gfx_line(im,
3457                                  im->xorigin + ii,
3458                                  im->yorigin - im->ysize - 1.0,
3459                                  im->xorigin + ii,
3460                                  im->yorigin - im->ysize -
3461                                                 im->gdes[i].
3462                                                 yrule *
3463                                  im->ysize, 1.0, im->gdes[i].col);
3464                     }
3465                 }
3466             }
3467             break;
3468         case GF_LINE:
3469         case GF_AREA:
3470         case GF_GRAD: {
3471             rrd_value_t diffval = im->maxval - im->minval;
3472             rrd_value_t maxlimit = im->maxval + 9 * diffval;
3473             rrd_value_t minlimit = im->minval - 9 * diffval;        
3474             for (ii = 0; ii < im->xsize; ii++) {
3475                 /* fix data points at oo and -oo */
3476                 if (isinf(im->gdes[i].p_data[ii])) {
3477                     if (im->gdes[i].p_data[ii] > 0) {
3478                         im->gdes[i].p_data[ii] = im->maxval;
3479                     } else {
3480                         im->gdes[i].p_data[ii] = im->minval;
3481                     }
3482                 }
3483                 /* some versions of cairo go unstable when trying
3484                    to draw way out of the canvas ... lets not even try */
3485                if (im->gdes[i].p_data[ii] > maxlimit) {
3486                    im->gdes[i].p_data[ii] = maxlimit;
3487                }
3488                if (im->gdes[i].p_data[ii] < minlimit) {
3489                    im->gdes[i].p_data[ii] = minlimit;
3490                }
3491             }           /* for */
3493             /* *******************************************************
3494                a           ___. (a,t)
3495                |   |    ___
3496                ____|   |   |   |
3497                |       |___|
3498                -------|--t-1--t--------------------------------
3500                if we know the value at time t was a then
3501                we draw a square from t-1 to t with the value a.
3503                ********************************************************* */
3504             if (im->gdes[i].col.alpha != 0.0) {
3505                 /* GF_LINE and friend */
3506                 if (im->gdes[i].gf == GF_LINE) {
3507                     double    last_y = 0.0;
3508                     int       draw_on = 0;
3510                     cairo_save(im->cr);
3511                     cairo_new_path(im->cr);
3512                     cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3513                     if (im->gdes[i].dash) {
3514                         cairo_set_dash(im->cr,
3515                                        im->gdes[i].p_dashes,
3516                                        im->gdes[i].ndash, im->gdes[i].offset);
3517                     }
3519                     for (ii = 1; ii < im->xsize; ii++) {
3520                         if (isnan(im->gdes[i].p_data[ii])
3521                             || (im->slopemode == 1
3522                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3523                             draw_on = 0;
3524                             continue;
3525                         }
3526                         if (draw_on == 0) {
3527                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3528                             if (im->slopemode == 0) {
3529                                 double    x = ii - 1 + im->xorigin;
3530                                 double    y = last_y;
3532                                 gfx_line_fit(im, &x, &y);
3533                                 cairo_move_to(im->cr, x, y);
3534                                 x = ii + im->xorigin;
3535                                 y = last_y;
3536                                 gfx_line_fit(im, &x, &y);
3537                                 cairo_line_to(im->cr, x, y);
3538                             } else {
3539                                 double    x = ii - 1 + im->xorigin;
3540                                 double    y =
3541                                     ytr(im, im->gdes[i].p_data[ii - 1]);
3542                                 gfx_line_fit(im, &x, &y);
3543                                 cairo_move_to(im->cr, x, y);
3544                                 x = ii + im->xorigin;
3545                                 y = last_y;
3546                                 gfx_line_fit(im, &x, &y);
3547                                 cairo_line_to(im->cr, x, y);
3548                             }
3549                             draw_on = 1;
3550                         } else {
3551                             double    x1 = ii + im->xorigin;
3552                             double    y1 = ytr(im, im->gdes[i].p_data[ii]);
3554                             if (im->slopemode == 0
3555                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3556                                 double    x = ii - 1 + im->xorigin;
3557                                 double    y = y1;
3559                                 gfx_line_fit(im, &x, &y);
3560                                 cairo_line_to(im->cr, x, y);
3561                             };
3562                             last_y = y1;
3563                             gfx_line_fit(im, &x1, &y1);
3564                             cairo_line_to(im->cr, x1, y1);
3565                         };
3566                     }
3567                     cairo_set_source_rgba(im->cr,
3568                                           im->gdes[i].
3569                                           col.red,
3570                                           im->gdes[i].
3571                                           col.green,
3572                                           im->gdes[i].
3573                                           col.blue, im->gdes[i].col.alpha);
3574                     cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3575                     cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3576                     cairo_stroke(im->cr);
3577                     cairo_restore(im->cr);
3578                 } else {
3579                                         double lastx=0;
3580                                         double lasty=0;
3581                     int       idxI = -1;
3582                     double   *foreY =
3583                         (double *) malloc(sizeof(double) * im->xsize * 2);
3584                     double   *foreX =
3585                         (double *) malloc(sizeof(double) * im->xsize * 2);
3586                     double   *backY =
3587                         (double *) malloc(sizeof(double) * im->xsize * 2);
3588                     double   *backX =
3589                         (double *) malloc(sizeof(double) * im->xsize * 2);
3590                     int       drawem = 0;
3592                     for (ii = 0; ii <= im->xsize; ii++) {
3593                         double    ybase, ytop;
3595                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3596                             int       cntI = 1;
3597                             int       lastI = 0;
3599                             while (cntI < idxI
3600                                    &&
3601                                    AlmostEqual2sComplement(foreY
3602                                                            [lastI],
3603                                                            foreY[cntI], 4)
3604                                    &&
3605                                    AlmostEqual2sComplement(foreY
3606                                                            [lastI],
3607                                                            foreY
3608                                                            [cntI + 1], 4)) {
3609                                 cntI++;
3610                             }
3611                                                         if (im->gdes[i].gf != GF_GRAD) {
3612                                 gfx_new_area(im,
3613                                              backX[0], backY[0],
3614                                              foreX[0], foreY[0],
3615                                              foreX[cntI],
3616                                              foreY[cntI], im->gdes[i].col);
3617                                                         } else {
3618                                                                 lastx = foreX[cntI];
3619                                                                 lasty = foreY[cntI];
3620                                                         }
3621                                                         while (cntI < idxI) {
3622                                 lastI = cntI;
3623                                 cntI++;
3624                                 while (cntI < idxI
3625                                        &&
3626                                        AlmostEqual2sComplement(foreY
3627                                                                [lastI],
3628                                                                foreY[cntI], 4)
3629                                        &&
3630                                        AlmostEqual2sComplement(foreY
3631                                                                [lastI],
3632                                                                foreY
3633                                                                [cntI
3634                                                                 + 1], 4)) {
3635                                     cntI++;
3636                                 }
3637                                                                 if (im->gdes[i].gf != GF_GRAD) {
3638                                         gfx_add_point(im, foreX[cntI], foreY[cntI]);
3639                                                                 } else {
3640                                                                         gfx_add_rect_fadey(im, 
3641                                                                                 lastx, foreY[0],
3642                                                                                 foreX[cntI], foreY[cntI], lasty, 
3643                                                                                 im->gdes[i].col,
3644                                                                                 im->gdes[i].col2,
3645                                                                                 im->gdes[i].gradheight
3646                                                                                 );
3647                                                                         lastx = foreX[cntI];
3648                                                                         lasty = foreY[cntI];
3649                                                                 }
3650                             }
3651                                                         if (im->gdes[i].gf != GF_GRAD) {
3652                                 gfx_add_point(im, backX[idxI], backY[idxI]);
3653                                                         } else {
3654                                                                 gfx_add_rect_fadey(im,
3655                                                                         lastx, foreY[0],
3656                                                                         backX[idxI], backY[idxI], lasty,
3657                                                                         im->gdes[i].col,
3658                                                                         im->gdes[i].col2,
3659                                                                         im->gdes[i].gradheight);
3660                                                                 lastx = backX[idxI];
3661                                                                 lasty = backY[idxI];
3662                                                         }
3663                             while (idxI > 1) {
3664                                 lastI = idxI;
3665                                 idxI--;
3666                                 while (idxI > 1
3667                                        &&
3668                                        AlmostEqual2sComplement(backY
3669                                                                [lastI],
3670                                                                backY[idxI], 4)
3671                                        &&
3672                                        AlmostEqual2sComplement(backY
3673                                                                [lastI],
3674                                                                backY
3675                                                                [idxI
3676                                                                 - 1], 4)) {
3677                                     idxI--;
3678                                 }
3679                                                                 if (im->gdes[i].gf != GF_GRAD) {
3680                                         gfx_add_point(im, backX[idxI], backY[idxI]);
3681                                                                 } else {
3682                                                                         gfx_add_rect_fadey(im,
3683                                                                                 lastx, foreY[0],
3684                                                                                 backX[idxI], backY[idxI], lasty,
3685                                                                                 im->gdes[i].col,
3686                                                                                 im->gdes[i].col2,
3687                                                                                 im->gdes[i].gradheight);
3688                                                                         lastx = backX[idxI];
3689                                                                         lasty = backY[idxI];
3690                                                                 }
3691                             }
3692                             idxI = -1;
3693                             drawem = 0;
3694                                                         if (im->gdes[i].gf != GF_GRAD) 
3695                                     gfx_close_path(im);
3696                         }
3697                         if (drawem != 0) {
3698                             drawem = 0;
3699                             idxI = -1;
3700                         }
3701                         if (ii == im->xsize)
3702                             break;
3703                         if (im->slopemode == 0 && ii == 0) {
3704                             continue;
3705                         }
3706                         if (isnan(im->gdes[i].p_data[ii])) {
3707                             drawem = 1;
3708                             continue;
3709                         }
3710                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3711                         if (lastgdes && im->gdes[i].stack) {
3712                             ybase = ytr(im, lastgdes->p_data[ii]);
3713                         } else {
3714                             ybase = ytr(im, areazero);
3715                         }
3716                         if (ybase == ytop) {
3717                             drawem = 1;
3718                             continue;
3719                         }
3721                         if (ybase > ytop) {
3722                             double    extra = ytop;
3724                             ytop = ybase;
3725                             ybase = extra;
3726                         }
3727                         if (im->slopemode == 0) {
3728                             backY[++idxI] = ybase - 0.2;
3729                             backX[idxI] = ii + im->xorigin - 1;
3730                             foreY[idxI] = ytop + 0.2;
3731                             foreX[idxI] = ii + im->xorigin - 1;
3732                         }
3733                         backY[++idxI] = ybase - 0.2;
3734                         backX[idxI] = ii + im->xorigin;
3735                         foreY[idxI] = ytop + 0.2;
3736                         foreX[idxI] = ii + im->xorigin;
3737                     }
3738                     /* close up any remaining area */
3739                     free(foreY);
3740                     free(foreX);
3741                     free(backY);
3742                     free(backX);
3743                 }       /* else GF_LINE */
3744             }
3745             /* if color != 0x0 */
3746             /* make sure we do not run into trouble when stacking on NaN */
3747             for (ii = 0; ii < im->xsize; ii++) {
3748                 if (isnan(im->gdes[i].p_data[ii])) {
3749                     if (lastgdes && (im->gdes[i].stack)) {
3750                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3751                     } else {
3752                         im->gdes[i].p_data[ii] = areazero;
3753                     }
3754                 }
3755             }
3756             lastgdes = &(im->gdes[i]);
3757             break;
3758         } /* GF_AREA, GF_LINE, GF_GRAD */
3759         case GF_STACK:
3760             rrd_set_error
3761                 ("STACK should already be turned into LINE or AREA here");
3762             return -1;
3763             break;
3764         }               /* switch */
3765     }
3766     cairo_reset_clip(im->cr);
3768     /* grid_paint also does the text */
3769     if (!(im->extra_flags & ONLY_GRAPH))
3770         grid_paint(im);
3771     if (!(im->extra_flags & ONLY_GRAPH))
3772         axis_paint(im);
3773     /* the RULES are the last thing to paint ... */
3774     for (i = 0; i < im->gdes_c; i++) {
3776         switch (im->gdes[i].gf) {
3777         case GF_HRULE:
3778             if (im->gdes[i].yrule >= im->minval
3779                 && im->gdes[i].yrule <= im->maxval) {
3780                 cairo_save(im->cr);
3781                 if (im->gdes[i].dash) {
3782                     cairo_set_dash(im->cr,
3783                                    im->gdes[i].p_dashes,
3784                                    im->gdes[i].ndash, im->gdes[i].offset);
3785                 }
3786                 gfx_line(im, im->xorigin,
3787                          ytr(im, im->gdes[i].yrule),
3788                          im->xorigin + im->xsize,
3789                          ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3790                 cairo_stroke(im->cr);
3791                 cairo_restore(im->cr);
3792             }
3793             break;
3794         case GF_VRULE:
3795             if (im->gdes[i].xrule >= im->start
3796                 && im->gdes[i].xrule <= im->end) {
3797                 cairo_save(im->cr);
3798                 if (im->gdes[i].dash) {
3799                     cairo_set_dash(im->cr,
3800                                    im->gdes[i].p_dashes,
3801                                    im->gdes[i].ndash, im->gdes[i].offset);
3802                 }
3803                 gfx_line(im,
3804                          xtr(im, im->gdes[i].xrule),
3805                          im->yorigin, xtr(im,
3806                                           im->
3807                                           gdes[i].
3808                                           xrule),
3809                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3810                 cairo_stroke(im->cr);
3811                 cairo_restore(im->cr);
3812             }
3813             break;
3814         default:
3815             break;
3816         }
3817     }
3820     switch (im->imgformat) {
3821     case IF_PNG:
3822     {
3823         cairo_status_t status;
3825         status = strlen(im->graphfile) ?
3826             cairo_surface_write_to_png(im->surface, im->graphfile)
3827             : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3828                                                 im);
3830         if (status != CAIRO_STATUS_SUCCESS) {
3831             rrd_set_error("Could not save png to '%s'", im->graphfile);
3832             return 1;
3833         }
3834         break;
3835     }
3836     default:
3837         if (strlen(im->graphfile)) {
3838             cairo_show_page(im->cr);
3839         } else {
3840             cairo_surface_finish(im->surface);
3841         }
3842         break;
3843     }
3845     return 0;
3849 /*****************************************************
3850  * graph stuff
3851  *****************************************************/
3853 int gdes_alloc(
3854     image_desc_t *im)
3857     im->gdes_c++;
3858     if ((im->gdes = (graph_desc_t *)
3859          rrd_realloc(im->gdes, (im->gdes_c)
3860                      * sizeof(graph_desc_t))) == NULL) {
3861         rrd_set_error("realloc graph_descs");
3862         return -1;
3863     }
3866     im->gdes[im->gdes_c - 1].step = im->step;
3867     im->gdes[im->gdes_c - 1].step_orig = im->step;
3868     im->gdes[im->gdes_c - 1].stack = 0;
3869     im->gdes[im->gdes_c - 1].linewidth = 0;
3870     im->gdes[im->gdes_c - 1].debug = 0;
3871     im->gdes[im->gdes_c - 1].start = im->start;
3872     im->gdes[im->gdes_c - 1].start_orig = im->start;
3873     im->gdes[im->gdes_c - 1].end = im->end;
3874     im->gdes[im->gdes_c - 1].end_orig = im->end;
3875     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3876     im->gdes[im->gdes_c - 1].data = NULL;
3877     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3878     im->gdes[im->gdes_c - 1].data_first = 0;
3879     im->gdes[im->gdes_c - 1].p_data = NULL;
3880     im->gdes[im->gdes_c - 1].rpnp = NULL;
3881     im->gdes[im->gdes_c - 1].p_dashes = NULL;
3882     im->gdes[im->gdes_c - 1].shift = 0.0;
3883     im->gdes[im->gdes_c - 1].dash = 0;
3884     im->gdes[im->gdes_c - 1].ndash = 0;
3885     im->gdes[im->gdes_c - 1].offset = 0;
3886     im->gdes[im->gdes_c - 1].col.red = 0.0;
3887     im->gdes[im->gdes_c - 1].col.green = 0.0;
3888     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3889     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3890     im->gdes[im->gdes_c - 1].col2.red = 0.0;
3891     im->gdes[im->gdes_c - 1].col2.green = 0.0;
3892     im->gdes[im->gdes_c - 1].col2.blue = 0.0;
3893     im->gdes[im->gdes_c - 1].col2.alpha = 0.0;
3894     im->gdes[im->gdes_c - 1].gradheight = 50.0;
3895     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3896     im->gdes[im->gdes_c - 1].format[0] = '\0';
3897     im->gdes[im->gdes_c - 1].strftm = 0;
3898     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3899     im->gdes[im->gdes_c - 1].ds = -1;
3900     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3901     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3902     im->gdes[im->gdes_c - 1].yrule = DNAN;
3903     im->gdes[im->gdes_c - 1].xrule = 0;
3904     im->gdes[im->gdes_c - 1].daemon[0] = 0;
3905     return 0;
3908 /* copies input untill the first unescaped colon is found
3909    or until input ends. backslashes have to be escaped as well */
3910 int scan_for_col(
3911     const char *const input,
3912     int len,
3913     char *const output)
3915     int       inp, outp = 0;
3917     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3918         if (input[inp] == '\\'
3919             && input[inp + 1] != '\0'
3920             && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3921             output[outp++] = input[++inp];
3922         } else {
3923             output[outp++] = input[inp];
3924         }
3925     }
3926     output[outp] = '\0';
3927     return inp;
3930 /* Now just a wrapper around rrd_graph_v */
3931 int rrd_graph(
3932     int argc,
3933     char **argv,
3934     char ***prdata,
3935     int *xsize,
3936     int *ysize,
3937     FILE * stream,
3938     double *ymin,
3939     double *ymax)
3941     int       prlines = 0;
3942     rrd_info_t *grinfo = NULL;
3943     rrd_info_t *walker;
3945     grinfo = rrd_graph_v(argc, argv);
3946     if (grinfo == NULL)
3947         return -1;
3948     walker = grinfo;
3949     (*prdata) = NULL;
3950     while (walker) {
3951         if (strcmp(walker->key, "image_info") == 0) {
3952             prlines++;
3953             if (((*prdata) =
3954                  (char**)rrd_realloc((*prdata),
3955                              (prlines + 1) * sizeof(char *))) == NULL) {
3956                 rrd_set_error("realloc prdata");
3957                 return 0;
3958             }
3959             /* imginfo goes to position 0 in the prdata array */
3960             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3961                                              + 2) * sizeof(char));
3962             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3963             (*prdata)[prlines] = NULL;
3964         }
3965         /* skip anything else */
3966         walker = walker->next;
3967     }
3968     walker = grinfo;
3969     *xsize = 0;
3970     *ysize = 0;
3971     *ymin = 0;
3972     *ymax = 0;
3973     while (walker) {
3974         if (strcmp(walker->key, "image_width") == 0) {
3975             *xsize = walker->value.u_cnt;
3976         } else if (strcmp(walker->key, "image_height") == 0) {
3977             *ysize = walker->value.u_cnt;
3978         } else if (strcmp(walker->key, "value_min") == 0) {
3979             *ymin = walker->value.u_val;
3980         } else if (strcmp(walker->key, "value_max") == 0) {
3981             *ymax = walker->value.u_val;
3982         } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3983             prlines++;
3984             if (((*prdata) =
3985                  (char**)rrd_realloc((*prdata),
3986                              (prlines + 1) * sizeof(char *))) == NULL) {
3987                 rrd_set_error("realloc prdata");
3988                 return 0;
3989             }
3990             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3991                                              + 2) * sizeof(char));
3992             (*prdata)[prlines] = NULL;
3993             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3994         } else if (strcmp(walker->key, "image") == 0) {
3995             if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3996                    (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3997                 rrd_set_error("writing image");
3998                 return 0;
3999             }
4000         }
4001         /* skip anything else */
4002         walker = walker->next;
4003     }
4004     rrd_info_free(grinfo);
4005     return 0;
4009 /* Some surgery done on this function, it became ridiculously big.
4010 ** Things moved:
4011 ** - initializing     now in rrd_graph_init()
4012 ** - options parsing  now in rrd_graph_options()
4013 ** - script parsing   now in rrd_graph_script()
4014 */
4015 rrd_info_t *rrd_graph_v(
4016     int argc,
4017     char **argv)
4019     image_desc_t im;
4020     rrd_info_t *grinfo;
4021     char *old_locale;
4022     rrd_graph_init(&im);
4023     /* a dummy surface so that we can measure text sizes for placements */
4024     old_locale = setlocale(LC_NUMERIC, NULL);
4025     setlocale(LC_NUMERIC, "C");
4026     rrd_graph_options(argc, argv, &im);
4027     if (rrd_test_error()) {
4028         setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4029         rrd_info_free(im.grinfo);
4030         im_free(&im);
4031         return NULL;
4032     }
4034     if (optind >= argc) {
4035         setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4036         rrd_info_free(im.grinfo);
4037         im_free(&im);
4038         rrd_set_error("missing filename");
4039         return NULL;
4040     }
4042     if (strlen(argv[optind]) >= MAXPATH) {
4043         setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4044         rrd_set_error("filename (including path) too long");
4045         rrd_info_free(im.grinfo);
4046         im_free(&im);
4047         return NULL;
4048     }
4050     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
4051     im.graphfile[MAXPATH - 1] = '\0';
4053     if (strcmp(im.graphfile, "-") == 0) {
4054         im.graphfile[0] = '\0';
4055     }
4057     rrd_graph_script(argc, argv, &im, 1);
4058     setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
4060     if (rrd_test_error()) {
4061         rrd_info_free(im.grinfo);
4062         im_free(&im);
4063         return NULL;
4064     }
4066     /* Everything is now read and the actual work can start */
4068     if (graph_paint(&im) == -1) {
4069         rrd_info_free(im.grinfo);
4070         im_free(&im);
4071         return NULL;
4072     }
4075     /* The image is generated and needs to be output.
4076      ** Also, if needed, print a line with information about the image.
4077      */
4079     if (im.imginfo) {
4080         rrd_infoval_t info;
4081         char     *path;
4082         char     *filename;
4084         path = strdup(im.graphfile);
4085         filename = basename(path);
4086         info.u_str =
4087             sprintf_alloc(im.imginfo,
4088                           filename,
4089                           (long) (im.zoom *
4090                                   im.ximg), (long) (im.zoom * im.yimg));
4091         grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
4092         free(info.u_str);
4093         free(path);
4094     }
4095     if (im.rendered_image) {
4096         rrd_infoval_t img;
4098         img.u_blo.size = im.rendered_image_size;
4099         img.u_blo.ptr = im.rendered_image;
4100         grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
4101     }
4102     grinfo = im.grinfo;
4103     im_free(&im);
4104     return grinfo;
4107 static void
4108 rrd_set_font_desc (
4109     image_desc_t *im,int prop,char *font, double size ){
4110     if (font){
4111         strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4112         im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4113         /* if we already got one, drop it first */
4114         pango_font_description_free(im->text_prop[prop].font_desc);
4115         im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4116     };
4117     if (size > 0){
4118         im->text_prop[prop].size = size;
4119     };
4120     if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4121         pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4122     };
4125 void rrd_graph_init(
4126     image_desc_t
4127     *im)
4129     unsigned int i;
4130     char     *deffont = getenv("RRD_DEFAULT_FONT");
4131     static PangoFontMap *fontmap = NULL;
4132     PangoContext *context;
4134 #ifdef HAVE_TZSET
4135     tzset();
4136 #endif
4138     im->base = 1000;
4139     im->daemon_addr = NULL;
4140     im->draw_x_grid = 1;
4141     im->draw_y_grid = 1;
4142     im->draw_3d_border = 2;
4143     im->dynamic_labels = 0;
4144     im->extra_flags = 0;
4145     im->font_options = cairo_font_options_create();
4146     im->forceleftspace = 0;
4147     im->gdes_c = 0;
4148     im->gdes = NULL;
4149     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4150     im->grid_dash_off = 1;
4151     im->grid_dash_on = 1;
4152     im->gridfit = 1;
4153     im->grinfo = (rrd_info_t *) NULL;
4154     im->grinfo_current = (rrd_info_t *) NULL;
4155     im->imgformat = IF_PNG;
4156     im->imginfo = NULL;
4157     im->lazy = 0;
4158     im->legenddirection = TOP_DOWN;
4159     im->legendheight = 0;
4160     im->legendposition = SOUTH;
4161     im->legendwidth = 0;
4162     im->logarithmic = 0;
4163     im->maxval = DNAN;
4164     im->minval = 0;
4165     im->minval = DNAN;
4166     im->magfact = 1;
4167     im->prt_c = 0;
4168     im->rigid = 0;
4169     im->rendered_image_size = 0;
4170     im->rendered_image = NULL;
4171     im->slopemode = 0;
4172     im->step = 0;
4173     im->symbol = ' ';
4174     im->tabwidth = 40.0;
4175     im->title[0] = '\0';
4176     im->unitsexponent = 9999;
4177     im->unitslength = 6;
4178     im->viewfactor = 1.0;
4179     im->watermark[0] = '\0';
4180     im->with_markup = 0;
4181     im->ximg = 0;
4182     im->xlab_user.minsec = -1;
4183     im->xorigin = 0;
4184     im->xOriginLegend = 0;
4185     im->xOriginLegendY = 0;
4186     im->xOriginLegendY2 = 0;
4187     im->xOriginTitle = 0;
4188     im->xsize = 400;
4189     im->ygridstep = DNAN;
4190     im->yimg = 0;
4191     im->ylegend[0] = '\0';
4192     im->second_axis_scale = 0; /* 0 disables it */
4193     im->second_axis_shift = 0; /* no shift by default */
4194     im->second_axis_legend[0] = '\0';
4195     im->second_axis_format[0] = '\0';
4196     im->yorigin = 0;
4197     im->yOriginLegend = 0;
4198     im->yOriginLegendY = 0;
4199     im->yOriginLegendY2 = 0;
4200     im->yOriginTitle = 0;
4201     im->ysize = 100;
4202     im->zoom = 1;
4204     im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4205     im->cr = cairo_create(im->surface);
4207     for (i = 0; i < DIM(text_prop); i++) {
4208         im->text_prop[i].size = -1;
4209         im->text_prop[i].font_desc = NULL;
4210         rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4211     }
4213     if (fontmap == NULL){
4214         fontmap = pango_cairo_font_map_get_default();
4215     }
4217     context =  pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4219     pango_cairo_context_set_resolution(context, 100);
4221     pango_cairo_update_context(im->cr,context);
4223     im->layout = pango_layout_new(context);
4224     g_object_unref (context);
4226 //  im->layout = pango_cairo_create_layout(im->cr);
4229     cairo_font_options_set_hint_style
4230         (im->font_options, CAIRO_HINT_STYLE_FULL);
4231     cairo_font_options_set_hint_metrics
4232         (im->font_options, CAIRO_HINT_METRICS_ON);
4233     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4237     for (i = 0; i < DIM(graph_col); i++)
4238         im->graph_col[i] = graph_col[i];
4244 void rrd_graph_options(
4245     int argc,
4246     char *argv[],
4247     image_desc_t
4248     *im)
4250     int       stroff;
4251     char     *parsetime_error = NULL;
4252     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4253     time_t    start_tmp = 0, end_tmp = 0;
4254     long      long_tmp;
4255     rrd_time_value_t start_tv, end_tv;
4256     long unsigned int color;
4258     /* defines for long options without a short equivalent. should be bytes,
4259        and may not collide with (the ASCII value of) short options */
4260 #define LONGOPT_UNITS_SI 255
4262 /* *INDENT-OFF* */
4263     struct option long_options[] = {
4264         { "alt-autoscale",      no_argument,       0, 'A'},
4265         { "imgformat",          required_argument, 0, 'a'},
4266         { "font-smoothing-threshold", required_argument, 0, 'B'},
4267         { "base",               required_argument, 0, 'b'},
4268         { "color",              required_argument, 0, 'c'},
4269         { "full-size-mode",     no_argument,       0, 'D'},
4270         { "daemon",             required_argument, 0, 'd'},
4271         { "slope-mode",         no_argument,       0, 'E'},
4272         { "end",                required_argument, 0, 'e'},
4273         { "force-rules-legend", no_argument,       0, 'F'},
4274         { "imginfo",            required_argument, 0, 'f'},
4275         { "graph-render-mode",  required_argument, 0, 'G'},
4276         { "no-legend",          no_argument,       0, 'g'},
4277         { "height",             required_argument, 0, 'h'},
4278         { "no-minor",           no_argument,       0, 'I'},
4279         { "interlaced",         no_argument,       0, 'i'},
4280         { "alt-autoscale-min",  no_argument,       0, 'J'},
4281         { "only-graph",         no_argument,       0, 'j'},
4282         { "units-length",       required_argument, 0, 'L'},
4283         { "lower-limit",        required_argument, 0, 'l'},
4284         { "alt-autoscale-max",  no_argument,       0, 'M'},
4285         { "zoom",               required_argument, 0, 'm'},
4286         { "no-gridfit",         no_argument,       0, 'N'},
4287         { "font",               required_argument, 0, 'n'},
4288         { "logarithmic",        no_argument,       0, 'o'},
4289         { "pango-markup",       no_argument,       0, 'P'},
4290         { "font-render-mode",   required_argument, 0, 'R'},
4291         { "rigid",              no_argument,       0, 'r'},
4292         { "step",               required_argument, 0, 'S'},
4293         { "start",              required_argument, 0, 's'},
4294         { "tabwidth",           required_argument, 0, 'T'},
4295         { "title",              required_argument, 0, 't'},
4296         { "upper-limit",        required_argument, 0, 'u'},
4297         { "vertical-label",     required_argument, 0, 'v'},
4298         { "watermark",          required_argument, 0, 'W'},
4299         { "width",              required_argument, 0, 'w'},
4300         { "units-exponent",     required_argument, 0, 'X'},
4301         { "x-grid",             required_argument, 0, 'x'},
4302         { "alt-y-grid",         no_argument,       0, 'Y'},
4303         { "y-grid",             required_argument, 0, 'y'},
4304         { "lazy",               no_argument,       0, 'z'},
4305         { "units",              required_argument, 0, LONGOPT_UNITS_SI},
4306         { "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 */
4307         { "disable-rrdtool-tag",no_argument,       0, 1001},
4308         { "right-axis",         required_argument, 0, 1002},
4309         { "right-axis-label",   required_argument, 0, 1003},
4310         { "right-axis-format",  required_argument, 0, 1004},
4311         { "legend-position",    required_argument, 0, 1005},
4312         { "legend-direction",   required_argument, 0, 1006},
4313         { "border",             required_argument, 0, 1007},
4314         { "grid-dash",          required_argument, 0, 1008},
4315         { "dynamic-labels",     no_argument,       0, 1009},
4316         { "week-fmt",           required_argument, 0, 1010},
4317         {  0, 0, 0, 0}
4318 };
4319 /* *INDENT-ON* */
4321     optind = 0;
4322     opterr = 0;         /* initialize getopt */
4323     rrd_parsetime("end-24h", &start_tv);
4324     rrd_parsetime("now", &end_tv);
4325     while (1) {
4326         int       option_index = 0;
4327         int       opt;
4328         int       col_start, col_end;
4330         opt = getopt_long(argc, argv,
4331                           "Aa:B:b:c:Dd:Ee:Ff:G:gh:IiJjL:l:Mm:Nn:oPR:rS:s:T:t:u:v:W:w:X:x:Yy:z",
4332                           long_options, &option_index);
4333         if (opt == EOF)
4334             break;
4335         switch (opt) {
4336         case 'I':
4337             im->extra_flags |= NOMINOR;
4338             break;
4339         case 'Y':
4340             im->extra_flags |= ALTYGRID;
4341             break;
4342         case 'A':
4343             im->extra_flags |= ALTAUTOSCALE;
4344             break;
4345         case 'J':
4346             im->extra_flags |= ALTAUTOSCALE_MIN;
4347             break;
4348         case 'M':
4349             im->extra_flags |= ALTAUTOSCALE_MAX;
4350             break;
4351         case 'j':
4352             im->extra_flags |= ONLY_GRAPH;
4353             break;
4354         case 'g':
4355             im->extra_flags |= NOLEGEND;
4356             break;
4357         case 1005:
4358             if (strcmp(optarg, "north") == 0) {
4359                 im->legendposition = NORTH;
4360             } else if (strcmp(optarg, "west") == 0) {
4361                 im->legendposition = WEST;
4362             } else if (strcmp(optarg, "south") == 0) {
4363                 im->legendposition = SOUTH;
4364             } else if (strcmp(optarg, "east") == 0) {
4365                 im->legendposition = EAST;
4366             } else {
4367                 rrd_set_error("unknown legend-position '%s'", optarg);
4368                 return;
4369             }
4370             break;
4371         case 1006:
4372             if (strcmp(optarg, "topdown") == 0) {
4373                 im->legenddirection = TOP_DOWN;
4374             } else if (strcmp(optarg, "bottomup") == 0) {
4375                 im->legenddirection = BOTTOM_UP;
4376             } else {
4377                 rrd_set_error("unknown legend-position '%s'", optarg);
4378                 return;
4379             }
4380             break;
4381         case 'F':
4382             im->extra_flags |= FORCE_RULES_LEGEND;
4383             break;
4384         case 1001:
4385             im->extra_flags |= NO_RRDTOOL_TAG;
4386             break;
4387         case LONGOPT_UNITS_SI:
4388             if (im->extra_flags & FORCE_UNITS) {
4389                 rrd_set_error("--units can only be used once!");
4390                 return;
4391             }
4392             if (strcmp(optarg, "si") == 0)
4393                 im->extra_flags |= FORCE_UNITS_SI;
4394             else {
4395                 rrd_set_error("invalid argument for --units: %s", optarg);
4396                 return;
4397             }
4398             break;
4399         case 'X':
4400             im->unitsexponent = atoi(optarg);
4401             break;
4402         case 'L':
4403             im->unitslength = atoi(optarg);
4404             im->forceleftspace = 1;
4405             break;
4406         case 'T':
4407             im->tabwidth = atof(optarg);
4408             break;
4409         case 'S':
4410             im->step = atoi(optarg);
4411             break;
4412         case 'N':
4413             im->gridfit = 0;
4414             break;
4415         case 'P':
4416             im->with_markup = 1;
4417             break;
4418         case 's':
4419             if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4420                 rrd_set_error("start time: %s", parsetime_error);
4421                 return;
4422             }
4423             break;
4424         case 'e':
4425             if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4426                 rrd_set_error("end time: %s", parsetime_error);
4427                 return;
4428             }
4429             break;
4430         case 'x':
4431             if (strcmp(optarg, "none") == 0) {
4432                 im->draw_x_grid = 0;
4433                 break;
4434             };
4435             if (sscanf(optarg,
4436                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4437                        scan_gtm,
4438                        &im->xlab_user.gridst,
4439                        scan_mtm,
4440                        &im->xlab_user.mgridst,
4441                        scan_ltm,
4442                        &im->xlab_user.labst,
4443                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4444                 strncpy(im->xlab_form, optarg + stroff,
4445                         sizeof(im->xlab_form) - 1);
4446                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4447                 if ((int)
4448                     (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4449                     rrd_set_error("unknown keyword %s", scan_gtm);
4450                     return;
4451                 } else if ((int)
4452                            (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4453                            == -1) {
4454                     rrd_set_error("unknown keyword %s", scan_mtm);
4455                     return;
4456                 } else if ((int)
4457                            (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4458                     rrd_set_error("unknown keyword %s", scan_ltm);
4459                     return;
4460                 }
4461                 im->xlab_user.minsec = 1;
4462                 im->xlab_user.stst = im->xlab_form;
4463             } else {
4464                 rrd_set_error("invalid x-grid format");
4465                 return;
4466             }
4467             break;
4468         case 'y':
4470             if (strcmp(optarg, "none") == 0) {
4471                 im->draw_y_grid = 0;
4472                 break;
4473             };
4474             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4475                 if (im->ygridstep <= 0) {
4476                     rrd_set_error("grid step must be > 0");
4477                     return;
4478                 } else if (im->ylabfact < 1) {
4479                     rrd_set_error("label factor must be > 0");
4480                     return;
4481                 }
4482             } else {
4483                 rrd_set_error("invalid y-grid format");
4484                 return;
4485             }
4486             break;
4487         case 1007:
4488             im->draw_3d_border = atoi(optarg);
4489             break;
4490         case 1008: /* grid-dash */
4491             if(sscanf(optarg,
4492                       "%lf:%lf",
4493                       &im->grid_dash_on,
4494                       &im->grid_dash_off) != 2) {
4495                 rrd_set_error("expected grid-dash format float:float");
4496                 return;
4497             }
4498             break;   
4499         case 1009: /* enable dynamic labels */
4500             im->dynamic_labels = 1;
4501             break;         
4502         case 1010:
4503             strncpy(week_fmt,optarg,sizeof week_fmt);
4504             week_fmt[(sizeof week_fmt)-1]='\0';
4505             break;
4506         case 1002: /* right y axis */
4508             if(sscanf(optarg,
4509                       "%lf:%lf",
4510                       &im->second_axis_scale,
4511                       &im->second_axis_shift) == 2) {
4512                 if(im->second_axis_scale==0){
4513                     rrd_set_error("the second_axis_scale  must not be 0");
4514                     return;
4515                 }
4516             } else {
4517                 rrd_set_error("invalid right-axis format expected scale:shift");
4518                 return;
4519             }
4520             break;
4521         case 1003:
4522             strncpy(im->second_axis_legend,optarg,150);
4523             im->second_axis_legend[150]='\0';
4524             break;
4525         case 1004:
4526             if (bad_format(optarg)){
4527                 rrd_set_error("use either %le or %lf formats");
4528                 return;
4529             }
4530             strncpy(im->second_axis_format,optarg,150);
4531             im->second_axis_format[150]='\0';
4532             break;
4533         case 'v':
4534             strncpy(im->ylegend, optarg, 150);
4535             im->ylegend[150] = '\0';
4536             break;
4537         case 'u':
4538             im->maxval = atof(optarg);
4539             break;
4540         case 'l':
4541             im->minval = atof(optarg);
4542             break;
4543         case 'b':
4544             im->base = atol(optarg);
4545             if (im->base != 1024 && im->base != 1000) {
4546                 rrd_set_error
4547                     ("the only sensible value for base apart from 1000 is 1024");
4548                 return;
4549             }
4550             break;
4551         case 'w':
4552             long_tmp = atol(optarg);
4553             if (long_tmp < 10) {
4554                 rrd_set_error("width below 10 pixels");
4555                 return;
4556             }
4557             im->xsize = long_tmp;
4558             break;
4559         case 'h':
4560             long_tmp = atol(optarg);
4561             if (long_tmp < 10) {
4562                 rrd_set_error("height below 10 pixels");
4563                 return;
4564             }
4565             im->ysize = long_tmp;
4566             break;
4567         case 'D':
4568             im->extra_flags |= FULL_SIZE_MODE;
4569             break;
4570         case 'i':
4571             /* interlaced png not supported at the moment */
4572             break;
4573         case 'r':
4574             im->rigid = 1;
4575             break;
4576         case 'f':
4577             im->imginfo = optarg;
4578             break;
4579         case 'a':
4580             if ((int)
4581                 (im->imgformat = if_conv(optarg)) == -1) {
4582                 rrd_set_error("unsupported graphics format '%s'", optarg);
4583                 return;
4584             }
4585             break;
4586         case 'z':
4587             im->lazy = 1;
4588             break;
4589         case 'E':
4590             im->slopemode = 1;
4591             break;
4592         case 'o':
4593             im->logarithmic = 1;
4594             break;
4595         case 'c':
4596             if (sscanf(optarg,
4597                        "%10[A-Z]#%n%8lx%n",
4598                        col_nam, &col_start, &color, &col_end) == 2) {
4599                 int       ci;
4600                 int       col_len = col_end - col_start;
4602                 switch (col_len) {
4603                 case 3:
4604                     color =
4605                         (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4606                                                          0x011000) |
4607                          ((color & 0x00F)
4608                           * 0x001100)
4609                          | 0x000000FF);
4610                     break;
4611                 case 4:
4612                     color =
4613                         (((color & 0xF000) *
4614                           0x11000) | ((color & 0x0F00) *
4615                                       0x01100) | ((color &
4616                                                    0x00F0) *
4617                                                   0x00110) |
4618                          ((color & 0x000F) * 0x00011)
4619                         );
4620                     break;
4621                 case 6:
4622                     color = (color << 8) + 0xff /* shift left by 8 */ ;
4623                     break;
4624                 case 8:
4625                     break;
4626                 default:
4627                     rrd_set_error("the color format is #RRGGBB[AA]");
4628                     return;
4629                 }
4630                 if ((ci = grc_conv(col_nam)) != -1) {
4631                     im->graph_col[ci] = gfx_hex_to_col(color);
4632                 } else {
4633                     rrd_set_error("invalid color name '%s'", col_nam);
4634                     return;
4635                 }
4636             } else {
4637                 rrd_set_error("invalid color def format");
4638                 return;
4639             }
4640             break;
4641         case 'n':{
4642             char      prop[15];
4643             double    size = 1;
4644             int       end;
4646             if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4647                 int       sindex, propidx;
4649                 if ((sindex = text_prop_conv(prop)) != -1) {
4650                     for (propidx = sindex;
4651                          propidx < TEXT_PROP_LAST; propidx++) {
4652                         if (size > 0) {
4653                             rrd_set_font_desc(im,propidx,NULL,size);
4654                         }
4655                         if ((int) strlen(optarg) > end+2) {
4656                             if (optarg[end] == ':') {
4657                                 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4658                             } else {
4659                                 rrd_set_error
4660                                     ("expected : after font size in '%s'",
4661                                      optarg);
4662                                 return;
4663                             }
4664                         }
4665                         /* only run the for loop for DEFAULT (0) for
4666                            all others, we break here. woodo programming */
4667                         if (propidx == sindex && sindex != 0)
4668                             break;
4669                     }
4670                 } else {
4671                     rrd_set_error("invalid fonttag '%s'", prop);
4672                     return;
4673                 }
4674             } else {
4675                 rrd_set_error("invalid text property format");
4676                 return;
4677             }
4678             break;
4679         }
4680         case 'm':
4681             im->zoom = atof(optarg);
4682             if (im->zoom <= 0.0) {
4683                 rrd_set_error("zoom factor must be > 0");
4684                 return;
4685             }
4686             break;
4687         case 't':
4688             strncpy(im->title, optarg, 150);
4689             im->title[150] = '\0';
4690             break;
4691         case 'R':
4692             if (strcmp(optarg, "normal") == 0) {
4693                 cairo_font_options_set_antialias
4694                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4695                 cairo_font_options_set_hint_style
4696                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4697             } else if (strcmp(optarg, "light") == 0) {
4698                 cairo_font_options_set_antialias
4699                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4700                 cairo_font_options_set_hint_style
4701                     (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4702             } else if (strcmp(optarg, "mono") == 0) {
4703                 cairo_font_options_set_antialias
4704                     (im->font_options, CAIRO_ANTIALIAS_NONE);
4705                 cairo_font_options_set_hint_style
4706                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4707             } else {
4708                 rrd_set_error("unknown font-render-mode '%s'", optarg);
4709                 return;
4710             }
4711             break;
4712         case 'G':
4713             if (strcmp(optarg, "normal") == 0)
4714                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4715             else if (strcmp(optarg, "mono") == 0)
4716                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4717             else {
4718                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4719                 return;
4720             }
4721             break;
4722         case 'B':
4723             /* not supported curently */
4724             break;
4725         case 'W':
4726             strncpy(im->watermark, optarg, 100);
4727             im->watermark[99] = '\0';
4728             break;
4729         case 'd':
4730         {
4731             if (im->daemon_addr != NULL)
4732             {
4733                 rrd_set_error ("You cannot specify --daemon "
4734                         "more than once.");
4735                 return;
4736             }
4738             im->daemon_addr = strdup(optarg);
4739             if (im->daemon_addr == NULL)
4740             {
4741               rrd_set_error("strdup failed");
4742               return;
4743             }
4745             break;
4746         }
4747         case '?':
4748             if (optopt != 0)
4749                 rrd_set_error("unknown option '%c'", optopt);
4750             else
4751                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4752             return;
4753         }
4754     } /* while (1) */
4756     pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4757     pango_layout_context_changed(im->layout);
4761     if (im->logarithmic && im->minval <= 0) {
4762         rrd_set_error
4763             ("for a logarithmic yaxis you must specify a lower-limit > 0");
4764         return;
4765     }
4767     if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4768         /* error string is set in rrd_parsetime.c */
4769         return;
4770     }
4772     if (start_tmp < 3600 * 24 * 365 * 10) {
4773         rrd_set_error
4774             ("the first entry to fetch should be after 1980 (%ld)",
4775              start_tmp);
4776         return;
4777     }
4779     if (end_tmp < start_tmp) {
4780         rrd_set_error
4781             ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4782         return;
4783     }
4785     im->start = start_tmp;
4786     im->end = end_tmp;
4787     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4790 int rrd_graph_color(
4791     image_desc_t
4792     *im,
4793     char *var,
4794     char *err,
4795     int optional)
4797     char     *color;
4798     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4800     color = strstr(var, "#");
4801     if (color == NULL) {
4802         if (optional == 0) {
4803             rrd_set_error("Found no color in %s", err);
4804             return 0;
4805         }
4806         return 0;
4807     } else {
4808         int       n = 0;
4809         char     *rest;
4810         long unsigned int col;
4812         rest = strstr(color, ":");
4813         if (rest != NULL)
4814             n = rest - color;
4815         else
4816             n = strlen(color);
4817         switch (n) {
4818         case 7:
4819             sscanf(color, "#%6lx%n", &col, &n);
4820             col = (col << 8) + 0xff /* shift left by 8 */ ;
4821             if (n != 7)
4822                 rrd_set_error("Color problem in %s", err);
4823             break;
4824         case 9:
4825             sscanf(color, "#%8lx%n", &col, &n);
4826             if (n == 9)
4827                 break;
4828         default:
4829             rrd_set_error("Color problem in %s", err);
4830         }
4831         if (rrd_test_error())
4832             return 0;
4833         gdp->col = gfx_hex_to_col(col);
4834         return n;
4835     }
4839 int bad_format(
4840     char *fmt)
4842     char     *ptr;
4843     int       n = 0;
4845     ptr = fmt;
4846     while (*ptr != '\0')
4847         if (*ptr++ == '%') {
4849             /* line cannot end with percent char */
4850             if (*ptr == '\0')
4851                 return 1;
4852             /* '%s', '%S' and '%%' are allowed */
4853             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4854                 ptr++;
4855             /* %c is allowed (but use only with vdef!) */
4856             else if (*ptr == 'c') {
4857                 ptr++;
4858                 n = 1;
4859             }
4861             /* or else '% 6.2lf' and such are allowed */
4862             else {
4863                 /* optional padding character */
4864                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4865                     ptr++;
4866                 /* This should take care of 'm.n' with all three optional */
4867                 while (*ptr >= '0' && *ptr <= '9')
4868                     ptr++;
4869                 if (*ptr == '.')
4870                     ptr++;
4871                 while (*ptr >= '0' && *ptr <= '9')
4872                     ptr++;
4873                 /* Either 'le', 'lf' or 'lg' must follow here */
4874                 if (*ptr++ != 'l')
4875                     return 1;
4876                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4877                     ptr++;
4878                 else
4879                     return 1;
4880                 n++;
4881             }
4882         }
4884     return (n != 1);
4888 int vdef_parse(
4889     struct graph_desc_t
4890     *gdes,
4891     const char *const str)
4893     /* A VDEF currently is either "func" or "param,func"
4894      * so the parsing is rather simple.  Change if needed.
4895      */
4896     double    param;
4897     char      func[30];
4898     int       n;
4900     n = 0;
4901     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4902     if (n == (int) strlen(str)) {   /* matched */
4903         ;
4904     } else {
4905         n = 0;
4906         sscanf(str, "%29[A-Z]%n", func, &n);
4907         if (n == (int) strlen(str)) {   /* matched */
4908             param = DNAN;
4909         } else {
4910             rrd_set_error
4911                 ("Unknown function string '%s' in VDEF '%s'",
4912                  str, gdes->vname);
4913             return -1;
4914         }
4915     }
4916     if (!strcmp("PERCENT", func))
4917         gdes->vf.op = VDEF_PERCENT;
4918     else if (!strcmp("PERCENTNAN", func))
4919         gdes->vf.op = VDEF_PERCENTNAN;
4920     else if (!strcmp("MAXIMUM", func))
4921         gdes->vf.op = VDEF_MAXIMUM;
4922     else if (!strcmp("AVERAGE", func))
4923         gdes->vf.op = VDEF_AVERAGE;
4924     else if (!strcmp("STDEV", func))
4925         gdes->vf.op = VDEF_STDEV;
4926     else if (!strcmp("MINIMUM", func))
4927         gdes->vf.op = VDEF_MINIMUM;
4928     else if (!strcmp("TOTAL", func))
4929         gdes->vf.op = VDEF_TOTAL;
4930     else if (!strcmp("FIRST", func))
4931         gdes->vf.op = VDEF_FIRST;
4932     else if (!strcmp("LAST", func))
4933         gdes->vf.op = VDEF_LAST;
4934     else if (!strcmp("LSLSLOPE", func))
4935         gdes->vf.op = VDEF_LSLSLOPE;
4936     else if (!strcmp("LSLINT", func))
4937         gdes->vf.op = VDEF_LSLINT;
4938     else if (!strcmp("LSLCORREL", func))
4939         gdes->vf.op = VDEF_LSLCORREL;
4940     else {
4941         rrd_set_error
4942             ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4943         return -1;
4944     };
4945     switch (gdes->vf.op) {
4946     case VDEF_PERCENT:
4947     case VDEF_PERCENTNAN:
4948         if (isnan(param)) { /* no parameter given */
4949             rrd_set_error
4950                 ("Function '%s' needs parameter in VDEF '%s'\n",
4951                  func, gdes->vname);
4952             return -1;
4953         };
4954         if (param >= 0.0 && param <= 100.0) {
4955             gdes->vf.param = param;
4956             gdes->vf.val = DNAN;    /* undefined */
4957             gdes->vf.when = 0;  /* undefined */
4958             gdes->vf.never = 1;
4959         } else {
4960             rrd_set_error
4961                 ("Parameter '%f' out of range in VDEF '%s'\n",
4962                  param, gdes->vname);
4963             return -1;
4964         };
4965         break;
4966     case VDEF_MAXIMUM:
4967     case VDEF_AVERAGE:
4968     case VDEF_STDEV:
4969     case VDEF_MINIMUM:
4970     case VDEF_TOTAL:
4971     case VDEF_FIRST:
4972     case VDEF_LAST:
4973     case VDEF_LSLSLOPE:
4974     case VDEF_LSLINT:
4975     case VDEF_LSLCORREL:
4976         if (isnan(param)) {
4977             gdes->vf.param = DNAN;
4978             gdes->vf.val = DNAN;
4979             gdes->vf.when = 0;
4980             gdes->vf.never = 1;
4981         } else {
4982             rrd_set_error
4983                 ("Function '%s' needs no parameter in VDEF '%s'\n",
4984                  func, gdes->vname);
4985             return -1;
4986         };
4987         break;
4988     };
4989     return 0;
4993 int vdef_calc(
4994     image_desc_t *im,
4995     int gdi)
4997     graph_desc_t *src, *dst;
4998     rrd_value_t *data;
4999     long      step, steps;
5001     dst = &im->gdes[gdi];
5002     src = &im->gdes[dst->vidx];
5003     data = src->data + src->ds;
5005     steps = (src->end - src->start) / src->step;
5006 #if 0
5007     printf
5008         ("DEBUG: start == %lu, end == %lu, %lu steps\n",
5009          src->start, src->end, steps);
5010 #endif
5011     switch (dst->vf.op) {
5012     case VDEF_PERCENT:{
5013         rrd_value_t *array;
5014         int       field;
5015         if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
5016             rrd_set_error("malloc VDEV_PERCENT");
5017             return -1;
5018         }
5019         for (step = 0; step < steps; step++) {
5020             array[step] = data[step * src->ds_cnt];
5021         }
5022         qsort(array, step, sizeof(double), vdef_percent_compar);
5023         field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
5024         dst->vf.val = array[field];
5025         dst->vf.when = 0;   /* no time component */
5026         dst->vf.never = 1;
5027         free(array);
5028 #if 0
5029         for (step = 0; step < steps; step++)
5030             printf("DEBUG: %3li:%10.2f %c\n",
5031                    step, array[step], step == field ? '*' : ' ');
5032 #endif
5033     }
5034         break;
5035     case VDEF_PERCENTNAN:{
5036         rrd_value_t *array;
5037         int       field;
5038        /* count number of "valid" values */
5039        int nancount=0;
5040        for (step = 0; step < steps; step++) {
5041          if (!isnan(data[step * src->ds_cnt])) { nancount++; }
5042        }
5043        /* and allocate it */
5044         if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
5045             rrd_set_error("malloc VDEV_PERCENT");
5046             return -1;
5047         }
5048        /* and fill it in */
5049        field=0;
5050         for (step = 0; step < steps; step++) {
5051            if (!isnan(data[step * src->ds_cnt])) {
5052                 array[field] = data[step * src->ds_cnt];
5053                field++;
5054             }
5055         }
5056         qsort(array, nancount, sizeof(double), vdef_percent_compar);
5057         field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
5058         dst->vf.val = array[field];
5059         dst->vf.when = 0;   /* no time component */
5060         dst->vf.never = 1;
5061         free(array);
5062     }
5063         break;
5064     case VDEF_MAXIMUM:
5065         step = 0;
5066         while (step != steps && isnan(data[step * src->ds_cnt]))
5067             step++;
5068         if (step == steps) {
5069             dst->vf.val = DNAN;
5070             dst->vf.when = 0;
5071             dst->vf.never = 1;
5072         } else {
5073             dst->vf.val = data[step * src->ds_cnt];
5074             dst->vf.when = src->start + (step + 1) * src->step;
5075             dst->vf.never = 0;
5076         }
5077         while (step != steps) {
5078             if (finite(data[step * src->ds_cnt])) {
5079                 if (data[step * src->ds_cnt] > dst->vf.val) {
5080                     dst->vf.val = data[step * src->ds_cnt];
5081                     dst->vf.when = src->start + (step + 1) * src->step;
5082                     dst->vf.never = 0;
5083                 }
5084             }
5085             step++;
5086         }
5087         break;
5088     case VDEF_TOTAL:
5089     case VDEF_STDEV:
5090     case VDEF_AVERAGE:{
5091         int       cnt = 0;
5092         double    sum = 0.0;
5093         double    average = 0.0;
5095         for (step = 0; step < steps; step++) {
5096             if (finite(data[step * src->ds_cnt])) {
5097                 sum += data[step * src->ds_cnt];
5098                 cnt++;
5099             };
5100         }
5101         if (cnt) {
5102             if (dst->vf.op == VDEF_TOTAL) {
5103                 dst->vf.val = sum * src->step;
5104                 dst->vf.when = 0;   /* no time component */
5105                 dst->vf.never = 1;
5106             } else if (dst->vf.op == VDEF_AVERAGE) {
5107                 dst->vf.val = sum / cnt;
5108                 dst->vf.when = 0;   /* no time component */
5109                 dst->vf.never = 1;
5110             } else {
5111                 average = sum / cnt;
5112                 sum = 0.0;
5113                 for (step = 0; step < steps; step++) {
5114                     if (finite(data[step * src->ds_cnt])) {
5115                         sum += pow((data[step * src->ds_cnt] - average), 2.0);
5116                     };
5117                 }
5118                 dst->vf.val = pow(sum / cnt, 0.5);
5119                 dst->vf.when = 0;   /* no time component */
5120                 dst->vf.never = 1;
5121             };
5122         } else {
5123             dst->vf.val = DNAN;
5124             dst->vf.when = 0;
5125             dst->vf.never = 1;
5126         }
5127     }
5128         break;
5129     case VDEF_MINIMUM:
5130         step = 0;
5131         while (step != steps && isnan(data[step * src->ds_cnt]))
5132             step++;
5133         if (step == steps) {
5134             dst->vf.val = DNAN;
5135             dst->vf.when = 0;
5136             dst->vf.never = 1;
5137         } else {
5138             dst->vf.val = data[step * src->ds_cnt];
5139             dst->vf.when = src->start + (step + 1) * src->step;
5140             dst->vf.never = 0;
5141         }
5142         while (step != steps) {
5143             if (finite(data[step * src->ds_cnt])) {
5144                 if (data[step * src->ds_cnt] < dst->vf.val) {
5145                     dst->vf.val = data[step * src->ds_cnt];
5146                     dst->vf.when = src->start + (step + 1) * src->step;
5147                     dst->vf.never = 0;
5148                 }
5149             }
5150             step++;
5151         }
5152         break;
5153     case VDEF_FIRST:
5154         /* The time value returned here is one step before the
5155          * actual time value.  This is the start of the first
5156          * non-NaN interval.
5157          */
5158         step = 0;
5159         while (step != steps && isnan(data[step * src->ds_cnt]))
5160             step++;
5161         if (step == steps) {    /* all entries were NaN */
5162             dst->vf.val = DNAN;
5163             dst->vf.when = 0;
5164             dst->vf.never = 1;
5165         } else {
5166             dst->vf.val = data[step * src->ds_cnt];
5167             dst->vf.when = src->start + step * src->step;
5168             dst->vf.never = 0;
5169         }
5170         break;
5171     case VDEF_LAST:
5172         /* The time value returned here is the
5173          * actual time value.  This is the end of the last
5174          * non-NaN interval.
5175          */
5176         step = steps - 1;
5177         while (step >= 0 && isnan(data[step * src->ds_cnt]))
5178             step--;
5179         if (step < 0) { /* all entries were NaN */
5180             dst->vf.val = DNAN;
5181             dst->vf.when = 0;
5182             dst->vf.never = 1;
5183         } else {
5184             dst->vf.val = data[step * src->ds_cnt];
5185             dst->vf.when = src->start + (step + 1) * src->step;
5186             dst->vf.never = 0;
5187         }
5188         break;
5189     case VDEF_LSLSLOPE:
5190     case VDEF_LSLINT:
5191     case VDEF_LSLCORREL:{
5192         /* Bestfit line by linear least squares method */
5194         int       cnt = 0;
5195         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5197         SUMx = 0;
5198         SUMy = 0;
5199         SUMxy = 0;
5200         SUMxx = 0;
5201         SUMyy = 0;
5202         for (step = 0; step < steps; step++) {
5203             if (finite(data[step * src->ds_cnt])) {
5204                 cnt++;
5205                 SUMx += step;
5206                 SUMxx += step * step;
5207                 SUMxy += step * data[step * src->ds_cnt];
5208                 SUMy += data[step * src->ds_cnt];
5209                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5210             };
5211         }
5213         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5214         y_intercept = (SUMy - slope * SUMx) / cnt;
5215         correl =
5216             (SUMxy -
5217              (SUMx * SUMy) / cnt) /
5218             sqrt((SUMxx -
5219                   (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5220         if (cnt) {
5221             if (dst->vf.op == VDEF_LSLSLOPE) {
5222                 dst->vf.val = slope;
5223                 dst->vf.when = 0;
5224                 dst->vf.never = 1;
5225             } else if (dst->vf.op == VDEF_LSLINT) {
5226                 dst->vf.val = y_intercept;
5227                 dst->vf.when = 0;
5228                 dst->vf.never = 1;
5229             } else if (dst->vf.op == VDEF_LSLCORREL) {
5230                 dst->vf.val = correl;
5231                 dst->vf.when = 0;
5232                 dst->vf.never = 1;
5233             };
5234         } else {
5235             dst->vf.val = DNAN;
5236             dst->vf.when = 0;
5237             dst->vf.never = 1;
5238         }
5239     }
5240         break;
5241     }
5242     return 0;
5245 /* NaN < -INF < finite_values < INF */
5246 int vdef_percent_compar(
5247     const void
5248     *a,
5249     const void
5250     *b)
5252     /* Equality is not returned; this doesn't hurt except
5253      * (maybe) for a little performance.
5254      */
5256     /* First catch NaN values. They are smallest */
5257     if (isnan(*(double *) a))
5258         return -1;
5259     if (isnan(*(double *) b))
5260         return 1;
5261     /* NaN doesn't reach this part so INF and -INF are extremes.
5262      * The sign from isinf() is compatible with the sign we return
5263      */
5264     if (isinf(*(double *) a))
5265         return isinf(*(double *) a);
5266     if (isinf(*(double *) b))
5267         return isinf(*(double *) b);
5268     /* If we reach this, both values must be finite */
5269     if (*(double *) a < *(double *) b)
5270         return -1;
5271     else
5272         return 1;
5275 void grinfo_push(
5276     image_desc_t *im,
5277     char *key,
5278     rrd_info_type_t type,
5279     rrd_infoval_t value)
5281     im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5282     if (im->grinfo == NULL) {
5283         im->grinfo = im->grinfo_current;
5284     }
5288 void time_clean(
5289     char *result,
5290     char *format)
5292     int       j, jj;
5293     
5294 /*     Handling based on
5295        - ANSI C99 Specifications                         http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
5296        - Single UNIX Specification version 2             http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html 
5297        - POSIX:2001/Single UNIX Specification version 3  http://www.opengroup.org/onlinepubs/009695399/functions/strftime.html
5298        - POSIX:2008 Specifications                       http://www.opengroup.org/onlinepubs/9699919799/functions/strftime.html
5299        Specifications tells 
5300        "If a conversion specifier is not one of the above, the behavior is undefined."
5302       C99 tells
5303        "A conversion specifier consists of a % character, possibly followed by an E or O modifier character (described below), followed by a character that determines the behavior of the conversion specifier.
5305       POSIX:2001 tells
5306       "A conversion specification consists of a '%' character, possibly followed by an E or O modifier, and a terminating conversion specifier character that determines the conversion specification's behavior."
5308       POSIX:2008 introduce more complexe behavior that are not handled here.
5310       According to this, this code will replace:
5311       - % followed by @ by a %@
5312       - % followed by   by a %SPACE
5313       - % followed by . by a %.
5314       - % followed by % by a %
5315       - % followed by t by a TAB
5316       - % followed by E then anything by '-'
5317       - % followed by O then anything by '-'
5318       - % followed by anything else by at least one '-'. More characters may be added to better fit expected output length
5319 */
5321     jj = 0;
5322     for(j = 0; (j < FMT_LEG_LEN - 1) && (jj < FMT_LEG_LEN); j++) { /* we don't need to parse the last char */
5323         if (format[j] == '%') {
5324             if ((format[j+1] == 'E') || (format[j+1] == 'O')) {
5325                 result[jj++] = '-';
5326                 j+=2; /* We skip next 2 following char */
5327             } else if ((format[j+1] == 'C') || (format[j+1] == 'd') ||
5328                        (format[j+1] == 'g') || (format[j+1] == 'H') ||
5329                        (format[j+1] == 'I') || (format[j+1] == 'm') ||
5330                        (format[j+1] == 'M') || (format[j+1] == 'S') ||
5331                        (format[j+1] == 'U') || (format[j+1] == 'V') ||
5332                        (format[j+1] == 'W') || (format[j+1] == 'y')) {
5333                 result[jj++] = '-';
5334                 if (jj < FMT_LEG_LEN) {
5335                     result[jj++] = '-';
5336                 }
5337                 j++; /* We skip the following char */
5338             } else if (format[j+1] == 'j') {
5339                 result[jj++] = '-';
5340                 if (jj < FMT_LEG_LEN - 1) {
5341                     result[jj++] = '-';
5342                     result[jj++] = '-';
5343                }
5344                 j++; /* We skip the following char */
5345             } else if ((format[j+1] == 'G') || (format[j+1] == 'Y')) {
5346                 /* Assuming Year on 4 digit */
5347                 result[jj++] = '-';
5348                 if (jj < FMT_LEG_LEN - 2) {
5349                     result[jj++] = '-';
5350                     result[jj++] = '-';
5351                     result[jj++] = '-';
5352                 }
5353                 j++; /* We skip the following char */
5354             } else if (format[j+1] == 'R') {
5355                 result[jj++] = '-';
5356                 if (jj < FMT_LEG_LEN - 3) {
5357                     result[jj++] = '-';
5358                     result[jj++] = ':';
5359                     result[jj++] = '-';
5360                     result[jj++] = '-';
5361                 }
5362                 j++; /* We skip the following char */
5363             } else if (format[j+1] == 'T') {
5364                 result[jj++] = '-';
5365                 if (jj < FMT_LEG_LEN - 6) {
5366                     result[jj++] = '-';
5367                     result[jj++] = ':';
5368                     result[jj++] = '-';
5369                     result[jj++] = '-';
5370                     result[jj++] = ':';
5371                     result[jj++] = '-';
5372                     result[jj++] = '-';
5373                 }
5374                 j++; /* We skip the following char */
5375             } else if (format[j+1] == 'F') {
5376                 result[jj++] = '-';
5377                 if (jj < FMT_LEG_LEN - 8) {
5378                     result[jj++] = '-';
5379                     result[jj++] = '-';
5380                     result[jj++] = '-';
5381                     result[jj++] = '-';
5382                     result[jj++] = '-';
5383                     result[jj++] = '-';
5384                     result[jj++] = '-';
5385                     result[jj++] = '-';
5386                     result[jj++] = '-';
5387                 }
5388                 j++; /* We skip the following char */
5389             } else if (format[j+1] == 'D') {
5390                 result[jj++] = '-';
5391                 if (jj < FMT_LEG_LEN - 6) {
5392                     result[jj++] = '-';
5393                     result[jj++] = '/';
5394                     result[jj++] = '-';
5395                     result[jj++] = '-';
5396                     result[jj++] = '/';
5397                     result[jj++] = '-';
5398                     result[jj++] = '-';
5399                 }
5400                 j++; /* We skip the following char */
5401             } else if (format[j+1] == 'n') {
5402                 result[jj++] = '\r';
5403                 result[jj++] = '\n';
5404                 j++; /* We skip the following char */
5405             } else if (format[j+1] == 't') {
5406                 result[jj++] = '\t';
5407                 j++; /* We skip the following char */
5408             } else if (format[j+1] == '%') {
5409                 result[jj++] = '%';
5410                 j++; /* We skip the following char */
5411             } else if (format[j+1] == ' ') {
5412                 if (jj < FMT_LEG_LEN - 1) {
5413                     result[jj++] = '%';
5414                     result[jj++] = ' ';
5415                 }
5416                 j++; /* We skip the following char */
5417             } else if (format[j+1] == '.') {
5418                 if (jj < FMT_LEG_LEN - 1) {
5419                     result[jj++] = '%';
5420                     result[jj++] = '.';
5421                 }
5422                 j++; /* We skip the following char */
5423             } else if (format[j+1] == '@') {
5424                 if (jj < FMT_LEG_LEN - 1) {
5425                     result[jj++] = '%';
5426                     result[jj++] = '@';
5427                 }
5428                 j++; /* We skip the following char */
5429             } else {
5430                 result[jj++] = '-';
5431                 j++; /* We skip the following char */
5432             }
5433         } else {
5434                 result[jj++] = format[j];
5435         }
5436     }
5437     result[jj] = '\0'; /* We must force the end of the string */