Code

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