Code

fix use of %s in strftime rrdgraph (G)PRINT commands ... bug (#277) found by Denis...
[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     do {
1467         switch (baseint) {
1468         case TMT_SECOND:
1469             tm.       tm_sec += basestep;
1471             break;
1472         case TMT_MINUTE:
1473             tm.       tm_min += basestep;
1475             break;
1476         case TMT_HOUR:
1477             tm.       tm_hour += basestep;
1479             break;
1480         case TMT_DAY:
1481             tm.       tm_mday += basestep;
1483             break;
1484         case TMT_WEEK:
1485             tm.       tm_mday += 7 * basestep;
1487             break;
1488         case TMT_MONTH:
1489             tm.       tm_mon += basestep;
1491             break;
1492         case TMT_YEAR:
1493             tm.       tm_year += basestep;
1494         }
1495         madetime = mktime(&tm);
1496     } while (madetime == -1);   /* this is necessary to skip impssible times
1497                                    like the daylight saving time skips */
1498     return madetime;
1503 /* calculate values required for PRINT and GPRINT functions */
1505 int print_calc(
1506     image_desc_t *im)
1508     long      i, ii, validsteps;
1509     double    printval;
1510     struct tm tmvdef;
1511     int       graphelement = 0;
1512     long      vidx;
1513     int       max_ii;
1514     double    magfact = -1;
1515     char     *si_symb = "";
1516     char     *percent_s;
1517     int       prline_cnt = 0;
1519     /* wow initializing tmvdef is quite a task :-) */
1520     time_t    now = time(NULL);
1522     localtime_r(&now, &tmvdef);
1523     for (i = 0; i < im->gdes_c; i++) {
1524         vidx = im->gdes[i].vidx;
1525         switch (im->gdes[i].gf) {
1526         case GF_PRINT:
1527         case GF_GPRINT:
1528             /* PRINT and GPRINT can now print VDEF generated values.
1529              * There's no need to do any calculations on them as these
1530              * calculations were already made.
1531              */
1532             if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1533                 printval = im->gdes[vidx].vf.val;
1534                 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1535             } else {    /* need to calculate max,min,avg etcetera */
1536                 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1537                           / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1538                 printval = DNAN;
1539                 validsteps = 0;
1540                 for (ii = im->gdes[vidx].ds;
1541                      ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1542                     if (!finite(im->gdes[vidx].data[ii]))
1543                         continue;
1544                     if (isnan(printval)) {
1545                         printval = im->gdes[vidx].data[ii];
1546                         validsteps++;
1547                         continue;
1548                     }
1550                     switch (im->gdes[i].cf) {
1551                     case CF_HWPREDICT:
1552                     case CF_MHWPREDICT:
1553                     case CF_DEVPREDICT:
1554                     case CF_DEVSEASONAL:
1555                     case CF_SEASONAL:
1556                     case CF_AVERAGE:
1557                         validsteps++;
1558                         printval += im->gdes[vidx].data[ii];
1559                         break;
1560                     case CF_MINIMUM:
1561                         printval = min(printval, im->gdes[vidx].data[ii]);
1562                         break;
1563                     case CF_FAILURES:
1564                     case CF_MAXIMUM:
1565                         printval = max(printval, im->gdes[vidx].data[ii]);
1566                         break;
1567                     case CF_LAST:
1568                         printval = im->gdes[vidx].data[ii];
1569                     }
1570                 }
1571                 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1572                     if (validsteps > 1) {
1573                         printval = (printval / validsteps);
1574                     }
1575                 }
1576             }           /* prepare printval */
1578             if (!im->gdes[i].strftm && (percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1579                 /* Magfact is set to -1 upon entry to print_calc.  If it
1580                  * is still less than 0, then we need to run auto_scale.
1581                  * Otherwise, put the value into the correct units.  If
1582                  * the value is 0, then do not set the symbol or magnification
1583                  * so next the calculation will be performed again. */
1584                 if (magfact < 0.0) {
1585                     auto_scale(im, &printval, &si_symb, &magfact);
1586                     if (printval == 0.0)
1587                         magfact = -1.0;
1588                 } else {
1589                     printval /= magfact;
1590                 }
1591                 *(++percent_s) = 's';
1592             } else if (!im->gdes[i].strftm && strstr(im->gdes[i].format, "%s") != NULL) {
1593                 auto_scale(im, &printval, &si_symb, &magfact);
1594             }
1596             if (im->gdes[i].gf == GF_PRINT) {
1597                 rrd_infoval_t prline;
1599                 if (im->gdes[i].strftm) {
1600                     prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1601                     strftime(prline.u_str,
1602                              FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1603                 } else if (bad_format(im->gdes[i].format)) {
1604                     rrd_set_error
1605                         ("bad format for PRINT in '%s'", im->gdes[i].format);
1606                     return -1;
1607                 } else {
1608                     prline.u_str =
1609                         sprintf_alloc(im->gdes[i].format, printval, si_symb);
1610                 }
1611                 grinfo_push(im,
1612                             sprintf_alloc
1613                             ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1614                 free(prline.u_str);
1615             } else {
1616                 /* GF_GPRINT */
1618                 if (im->gdes[i].strftm) {
1619                     strftime(im->gdes[i].legend,
1620                              FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1621                 } else {
1622                     if (bad_format(im->gdes[i].format)) {
1623                         rrd_set_error
1624                             ("bad format for GPRINT in '%s'",
1625                              im->gdes[i].format);
1626                         return -1;
1627                     }
1628 #ifdef HAVE_SNPRINTF
1629                     snprintf(im->gdes[i].legend,
1630                              FMT_LEG_LEN - 2,
1631                              im->gdes[i].format, printval, si_symb);
1632 #else
1633                     sprintf(im->gdes[i].legend,
1634                             im->gdes[i].format, printval, si_symb);
1635 #endif
1636                 }
1637                 graphelement = 1;
1638             }
1639             break;
1640         case GF_LINE:
1641         case GF_AREA:
1642         case GF_TICK:
1643             graphelement = 1;
1644             break;
1645         case GF_HRULE:
1646             if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1647                 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1648             };
1649             graphelement = 1;
1650             break;
1651         case GF_VRULE:
1652             if (im->gdes[i].xrule == 0) {   /* again ... the legend printer needs it */
1653                 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1654             };
1655             graphelement = 1;
1656             break;
1657         case GF_COMMENT:
1658         case GF_TEXTALIGN:
1659         case GF_DEF:
1660         case GF_CDEF:
1661         case GF_VDEF:
1662 #ifdef WITH_PIECHART
1663         case GF_PART:
1664 #endif
1665         case GF_SHIFT:
1666         case GF_XPORT:
1667             break;
1668         case GF_STACK:
1669             rrd_set_error
1670                 ("STACK should already be turned into LINE or AREA here");
1671             return -1;
1672             break;
1673         }
1674     }
1675     return graphelement;
1680 /* place legends with color spots */
1681 int leg_place(
1682     image_desc_t *im,
1683     int calc_width)
1685     /* graph labels */
1686     int       interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1687     int       border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1688     int       fill = 0, fill_last;
1689     double    legendwidth; // = im->ximg - 2 * border;
1690     int       leg_c = 0;
1691     double    leg_x = border;
1692     int       leg_y = 0; //im->yimg;
1693     int       leg_y_prev = 0; // im->yimg;
1694     int       leg_cc;
1695     double    glue = 0;
1696     int       i, ii, mark = 0;
1697     char      default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1698     int      *legspace;
1699     char     *tab;
1700     char      saved_legend[FMT_LEG_LEN + 5];
1702     if(calc_width){
1703         legendwidth = 0;
1704     }
1705     else{
1706         legendwidth = im->legendwidth - 2 * border;
1707     }
1710     if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1711         if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1712             rrd_set_error("malloc for legspace");
1713             return -1;
1714         }
1716         for (i = 0; i < im->gdes_c; i++) {
1717             char      prt_fctn; /*special printfunctions */
1718             if(calc_width){
1719                 strcpy(saved_legend, im->gdes[i].legend);
1720             }
1722             fill_last = fill;
1723             /* hide legends for rules which are not displayed */
1724             if (im->gdes[i].gf == GF_TEXTALIGN) {
1725                 default_txtalign = im->gdes[i].txtalign;
1726             }
1728             if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1729                 if (im->gdes[i].gf == GF_HRULE
1730                     && (im->gdes[i].yrule <
1731                         im->minval || im->gdes[i].yrule > im->maxval))
1732                     im->gdes[i].legend[0] = '\0';
1733                 if (im->gdes[i].gf == GF_VRULE
1734                     && (im->gdes[i].xrule <
1735                         im->start || im->gdes[i].xrule > im->end))
1736                     im->gdes[i].legend[0] = '\0';
1737             }
1739             /* turn \\t into tab */
1740             while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1741                 memmove(tab, tab + 1, strlen(tab));
1742                 tab[0] = (char) 9;
1743             }
1745             leg_cc = strlen(im->gdes[i].legend);
1746             /* is there a controle code at the end of the legend string ? */
1747             if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1748                 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1749                 leg_cc -= 2;
1750                 im->gdes[i].legend[leg_cc] = '\0';
1751             } else {
1752                 prt_fctn = '\0';
1753             }
1754             /* only valid control codes */
1755             if (prt_fctn != 'l' && prt_fctn != 'n' &&   /* a synonym for l */
1756                 prt_fctn != 'r' &&
1757                 prt_fctn != 'j' &&
1758                 prt_fctn != 'c' &&
1759                 prt_fctn != 'u' &&
1760                 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1761                 free(legspace);
1762                 rrd_set_error
1763                     ("Unknown control code at the end of '%s\\%c'",
1764                      im->gdes[i].legend, prt_fctn);
1765                 return -1;
1766             }
1767             /* \n -> \l */
1768             if (prt_fctn == 'n') {
1769                 prt_fctn = 'l';
1770             }
1772             /* remove exess space from the end of the legend for \g */
1773             while (prt_fctn == 'g' &&
1774                    leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1775                 leg_cc--;
1776                 im->gdes[i].legend[leg_cc] = '\0';
1777             }
1779             if (leg_cc != 0) {
1781                 /* no interleg space if string ends in \g */
1782                 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1783                 if (fill > 0) {
1784                     fill += legspace[i];
1785                 }
1786                 fill +=
1787                     gfx_get_text_width(im,
1788                                        fill + border,
1789                                        im->
1790                                        text_prop
1791                                        [TEXT_PROP_LEGEND].
1792                                        font_desc,
1793                                        im->tabwidth, im->gdes[i].legend);
1794                 leg_c++;
1795             } else {
1796                 legspace[i] = 0;
1797             }
1798             /* who said there was a special tag ... ? */
1799             if (prt_fctn == 'g') {
1800                 prt_fctn = '\0';
1801             }
1803             if (prt_fctn == '\0') {
1804                 if(calc_width && (fill > legendwidth)){
1805                     legendwidth = fill;
1806                 }
1807                 if (i == im->gdes_c - 1 || fill > legendwidth) {
1808                     /* just one legend item is left right or center */
1809                     switch (default_txtalign) {
1810                     case TXA_RIGHT:
1811                         prt_fctn = 'r';
1812                         break;
1813                     case TXA_CENTER:
1814                         prt_fctn = 'c';
1815                         break;
1816                     case TXA_JUSTIFIED:
1817                         prt_fctn = 'j';
1818                         break;
1819                     default:
1820                         prt_fctn = 'l';
1821                         break;
1822                     }
1823                 }
1824                 /* is it time to place the legends ? */
1825                 if (fill > legendwidth) {
1826                     if (leg_c > 1) {
1827                         /* go back one */
1828                         i--;
1829                         fill = fill_last;
1830                         leg_c--;
1831                     }
1832                 }
1833                 if (leg_c == 1 && prt_fctn == 'j') {
1834                     prt_fctn = 'l';
1835                 }
1836             }
1838             if (prt_fctn != '\0') {
1839                 leg_x = border;
1840                 if (leg_c >= 2 && prt_fctn == 'j') {
1841                     glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1842                 } else {
1843                     glue = 0;
1844                 }
1845                 if (prt_fctn == 'c')
1846                     leg_x = (double)(legendwidth - fill) / 2.0;
1847                 if (prt_fctn == 'r')
1848                     leg_x = legendwidth - fill + border;
1849                 for (ii = mark; ii <= i; ii++) {
1850                     if (im->gdes[ii].legend[0] == '\0')
1851                         continue;   /* skip empty legends */
1852                     im->gdes[ii].leg_x = leg_x;
1853                     im->gdes[ii].leg_y = leg_y + border;
1854                     leg_x +=
1855                         (double)gfx_get_text_width(im, leg_x,
1856                                            im->
1857                                            text_prop
1858                                            [TEXT_PROP_LEGEND].
1859                                            font_desc,
1860                                            im->tabwidth, im->gdes[ii].legend)
1861                         +(double)legspace[ii]
1862                         + glue;
1863                 }
1864                 leg_y_prev = leg_y;
1865                 if (leg_x > border || prt_fctn == 's')
1866                     leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1867                 if (prt_fctn == 's')
1868                     leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1869                 if (prt_fctn == 'u')
1870                     leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1872                 if(calc_width && (fill > legendwidth)){
1873                     legendwidth = fill;
1874                 }
1875                 fill = 0;
1876                 leg_c = 0;
1877                 mark = ii;
1878             }
1880             if(calc_width){
1881                 strcpy(im->gdes[i].legend, saved_legend);
1882             }
1883         }
1885         if(calc_width){
1886             im->legendwidth = legendwidth + 2 * border;
1887         }
1888         else{
1889             im->legendheight = leg_y + border * 0.6;
1890         }
1891         free(legspace);
1892     }
1893     return 0;
1896 /* create a grid on the graph. it determines what to do
1897    from the values of xsize, start and end */
1899 /* the xaxis labels are determined from the number of seconds per pixel
1900    in the requested graph */
1902 int calc_horizontal_grid(
1903     image_desc_t
1904     *im)
1906     double    range;
1907     double    scaledrange;
1908     int       pixel, i;
1909     int       gridind = 0;
1910     int       decimals, fractionals;
1912     im->ygrid_scale.labfact = 2;
1913     range = im->maxval - im->minval;
1914     scaledrange = range / im->magfact;
1915     /* does the scale of this graph make it impossible to put lines
1916        on it? If so, give up. */
1917     if (isnan(scaledrange)) {
1918         return 0;
1919     }
1921     /* find grid spaceing */
1922     pixel = 1;
1923     if (isnan(im->ygridstep)) {
1924         if (im->extra_flags & ALTYGRID) {
1925             /* find the value with max number of digits. Get number of digits */
1926             decimals =
1927                 ceil(log10
1928                      (max(fabs(im->maxval), fabs(im->minval)) *
1929                       im->viewfactor / im->magfact));
1930             if (decimals <= 0)  /* everything is small. make place for zero */
1931                 decimals = 1;
1932             im->ygrid_scale.gridstep =
1933                 pow((double) 10,
1934                     floor(log10(range * im->viewfactor / im->magfact))) /
1935                 im->viewfactor * im->magfact;
1936             if (im->ygrid_scale.gridstep == 0)  /* range is one -> 0.1 is reasonable scale */
1937                 im->ygrid_scale.gridstep = 0.1;
1938             /* should have at least 5 lines but no more then 15 */
1939             if (range / im->ygrid_scale.gridstep < 5
1940                 && im->ygrid_scale.gridstep >= 30)
1941                 im->ygrid_scale.gridstep /= 10;
1942             if (range / im->ygrid_scale.gridstep > 15)
1943                 im->ygrid_scale.gridstep *= 10;
1944             if (range / im->ygrid_scale.gridstep > 5) {
1945                 im->ygrid_scale.labfact = 1;
1946                 if (range / im->ygrid_scale.gridstep > 8
1947                     || im->ygrid_scale.gridstep <
1948                     1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1949                     im->ygrid_scale.labfact = 2;
1950             } else {
1951                 im->ygrid_scale.gridstep /= 5;
1952                 im->ygrid_scale.labfact = 5;
1953             }
1954             fractionals =
1955                 floor(log10
1956                       (im->ygrid_scale.gridstep *
1957                        (double) im->ygrid_scale.labfact * im->viewfactor /
1958                        im->magfact));
1959             if (fractionals < 0) {  /* small amplitude. */
1960                 int       len = decimals - fractionals + 1;
1962                 if (im->unitslength < len + 2)
1963                     im->unitslength = len + 2;
1964                 sprintf(im->ygrid_scale.labfmt,
1965                         "%%%d.%df%s", len,
1966                         -fractionals, (im->symbol != ' ' ? " %c" : ""));
1967             } else {
1968                 int       len = decimals + 1;
1970                 if (im->unitslength < len + 2)
1971                     im->unitslength = len + 2;
1972                 sprintf(im->ygrid_scale.labfmt,
1973                         "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1974             }
1975         } else {        /* classic rrd grid */
1976             for (i = 0; ylab[i].grid > 0; i++) {
1977                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1978                 gridind = i;
1979                 if (pixel >= 5)
1980                     break;
1981             }
1983             for (i = 0; i < 4; i++) {
1984                 if (pixel * ylab[gridind].lfac[i] >=
1985                     1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1986                     im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1987                     break;
1988                 }
1989             }
1991             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1992         }
1993     } else {
1994         im->ygrid_scale.gridstep = im->ygridstep;
1995         im->ygrid_scale.labfact = im->ylabfact;
1996     }
1997     return 1;
2000 int draw_horizontal_grid(
2001     image_desc_t
2002     *im)
2004     int       i;
2005     double    scaledstep;
2006     char      graph_label[100];
2007     int       nlabels = 0;
2008     double    X0 = im->xorigin;
2009     double    X1 = im->xorigin + im->xsize;
2010     int       sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2011     int       egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2012     double    MaxY;
2013     double second_axis_magfact = 0;
2014     char *second_axis_symb = "";
2016     scaledstep =
2017         im->ygrid_scale.gridstep /
2018         (double) im->magfact * (double) im->viewfactor;
2019     MaxY = scaledstep * (double) egrid;
2020     for (i = sgrid; i <= egrid; i++) {
2021         double    Y0 = ytr(im,
2022                            im->ygrid_scale.gridstep * i);
2023         double    YN = ytr(im,
2024                            im->ygrid_scale.gridstep * (i + 1));
2026         if (floor(Y0 + 0.5) >=
2027             im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2028             /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2029                with the chosen settings. Add a label if required by settings, or if
2030                there is only one label so far and the next grid line is out of bounds. */
2031             if (i % im->ygrid_scale.labfact == 0
2032                 || (nlabels == 1
2033                     && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2034                 if (im->symbol == ' ') {
2035                     if (im->extra_flags & ALTYGRID) {
2036                         sprintf(graph_label,
2037                                 im->ygrid_scale.labfmt,
2038                                 scaledstep * (double) i);
2039                     } else {
2040                         if (MaxY < 10) {
2041                             sprintf(graph_label, "%4.1f",
2042                                     scaledstep * (double) i);
2043                         } else {
2044                             sprintf(graph_label, "%4.0f",
2045                                     scaledstep * (double) i);
2046                         }
2047                     }
2048                 } else {
2049                     char      sisym = (i == 0 ? ' ' : im->symbol);
2051                     if (im->extra_flags & ALTYGRID) {
2052                         sprintf(graph_label,
2053                                 im->ygrid_scale.labfmt,
2054                                 scaledstep * (double) i, sisym);
2055                     } else {
2056                         if (MaxY < 10) {
2057                             sprintf(graph_label, "%4.1f %c",
2058                                     scaledstep * (double) i, sisym);
2059                         } else {
2060                             sprintf(graph_label, "%4.0f %c",
2061                                     scaledstep * (double) i, sisym);
2062                         }
2063                     }
2064                 }
2065                 nlabels++;
2066                 if (im->second_axis_scale != 0){
2067                         char graph_label_right[100];
2068                         double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2069                         if (im->second_axis_format[0] == '\0'){
2070                             if (!second_axis_magfact){
2071                                 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2072                                 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2073                             }
2074                             sval /= second_axis_magfact;
2076                             if(MaxY < 10) {
2077                                 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2078                             } else {
2079                                 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2080                             }
2081                         }
2082                         else {
2083                            sprintf(graph_label_right,im->second_axis_format,sval);
2084                         }
2085                         gfx_text ( im,
2086                                X1+7, Y0,
2087                                im->graph_col[GRC_FONT],
2088                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2089                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2090                                graph_label_right );
2091                 }
2093                 gfx_text(im,
2094                          X0 -
2095                          im->
2096                          text_prop[TEXT_PROP_AXIS].
2097                          size, Y0,
2098                          im->graph_col[GRC_FONT],
2099                          im->
2100                          text_prop[TEXT_PROP_AXIS].
2101                          font_desc,
2102                          im->tabwidth, 0.0,
2103                          GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2104                 gfx_line(im, X0 - 2, Y0, X0, Y0,
2105                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2106                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2107                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2108                 gfx_dashed_line(im, X0 - 2, Y0,
2109                                 X1 + 2, Y0,
2110                                 MGRIDWIDTH,
2111                                 im->
2112                                 graph_col
2113                                 [GRC_MGRID],
2114                                 im->grid_dash_on, im->grid_dash_off);
2115             } else if (!(im->extra_flags & NOMINOR)) {
2116                 gfx_line(im,
2117                          X0 - 2, Y0,
2118                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2119                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2120                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2121                 gfx_dashed_line(im, X0 - 1, Y0,
2122                                 X1 + 1, Y0,
2123                                 GRIDWIDTH,
2124                                 im->
2125                                 graph_col[GRC_GRID],
2126                                 im->grid_dash_on, im->grid_dash_off);
2127             }
2128         }
2129     }
2130     return 1;
2133 /* this is frexp for base 10 */
2134 double    frexp10(
2135     double,
2136     double *);
2137 double frexp10(
2138     double x,
2139     double *e)
2141     double    mnt;
2142     int       iexp;
2144     iexp = floor(log((double)fabs(x)) / log((double)10));
2145     mnt = x / pow(10.0, iexp);
2146     if (mnt >= 10.0) {
2147         iexp++;
2148         mnt = x / pow(10.0, iexp);
2149     }
2150     *e = iexp;
2151     return mnt;
2155 /* logaritmic horizontal grid */
2156 int horizontal_log_grid(
2157     image_desc_t
2158     *im)
2160     double    yloglab[][10] = {
2161         {
2162          1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2163          0.0, 0.0, 0.0}, {
2164                           1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2165                           0.0, 0.0, 0.0}, {
2166                                            1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2167                                            0.0, 0.0, 0.0}, {
2168                                                             1.0, 2.0, 4.0,
2169                                                             6.0, 8.0, 10.,
2170                                                             0.0,
2171                                                             0.0, 0.0, 0.0}, {
2172                                                                              1.0,
2173                                                                              2.0,
2174                                                                              3.0,
2175                                                                              4.0,
2176                                                                              5.0,
2177                                                                              6.0,
2178                                                                              7.0,
2179                                                                              8.0,
2180                                                                              9.0,
2181                                                                              10.},
2182         {
2183          0, 0, 0, 0, 0, 0, 0, 0, 0, 0}  /* last line */
2184     };
2185     int       i, j, val_exp, min_exp;
2186     double    nex;      /* number of decades in data */
2187     double    logscale; /* scale in logarithmic space */
2188     int       exfrac = 1;   /* decade spacing */
2189     int       mid = -1; /* row in yloglab for major grid */
2190     double    mspac;    /* smallest major grid spacing (pixels) */
2191     int       flab;     /* first value in yloglab to use */
2192     double    value, tmp, pre_value;
2193     double    X0, X1, Y0;
2194     char      graph_label[100];
2196     nex = log10(im->maxval / im->minval);
2197     logscale = im->ysize / nex;
2198     /* major spacing for data with high dynamic range */
2199     while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2200         if (exfrac == 1)
2201             exfrac = 3;
2202         else
2203             exfrac += 3;
2204     }
2206     /* major spacing for less dynamic data */
2207     do {
2208         /* search best row in yloglab */
2209         mid++;
2210         for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2211         mspac = logscale * log10(10.0 / yloglab[mid][i]);
2212     }
2213     while (mspac >
2214            2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2215     if (mid)
2216         mid--;
2217     /* find first value in yloglab */
2218     for (flab = 0;
2219          yloglab[mid][flab] < 10
2220          && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2221     if (yloglab[mid][flab] == 10.0) {
2222         tmp += 1.0;
2223         flab = 0;
2224     }
2225     val_exp = tmp;
2226     if (val_exp % exfrac)
2227         val_exp += abs(-val_exp % exfrac);
2228     X0 = im->xorigin;
2229     X1 = im->xorigin + im->xsize;
2230     /* draw grid */
2231     pre_value = DNAN;
2232     while (1) {
2234         value = yloglab[mid][flab] * pow(10.0, val_exp);
2235         if (AlmostEqual2sComplement(value, pre_value, 4))
2236             break;      /* it seems we are not converging */
2237         pre_value = value;
2238         Y0 = ytr(im, value);
2239         if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2240             break;
2241         /* major grid line */
2242         gfx_line(im,
2243                  X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2244         gfx_line(im, X1, Y0, X1 + 2, Y0,
2245                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2246         gfx_dashed_line(im, X0 - 2, Y0,
2247                         X1 + 2, Y0,
2248                         MGRIDWIDTH,
2249                         im->
2250                         graph_col
2251                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2252         /* label */
2253         if (im->extra_flags & FORCE_UNITS_SI) {
2254             int       scale;
2255             double    pvalue;
2256             char      symbol;
2258             scale = floor(val_exp / 3.0);
2259             if (value >= 1.0)
2260                 pvalue = pow(10.0, val_exp % 3);
2261             else
2262                 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2263             pvalue *= yloglab[mid][flab];
2264             if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2265                 && ((scale + si_symbcenter) >= 0))
2266                 symbol = si_symbol[scale + si_symbcenter];
2267             else
2268                 symbol = '?';
2269             sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2270         } else {
2271             sprintf(graph_label, "%3.0e", value);
2272         }
2273         if (im->second_axis_scale != 0){
2274                 char graph_label_right[100];
2275                 double sval = value*im->second_axis_scale+im->second_axis_shift;
2276                 if (im->second_axis_format[0] == '\0'){
2277                         if (im->extra_flags & FORCE_UNITS_SI) {
2278                                 double mfac = 1;
2279                                 char   *symb = "";
2280                                 auto_scale(im,&sval,&symb,&mfac);
2281                                 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2282                         }
2283                         else {
2284                                 sprintf(graph_label_right,"%3.0e", sval);
2285                         }
2286                 }
2287                 else {
2288                       sprintf(graph_label_right,im->second_axis_format,sval,"");
2289                 }
2291                 gfx_text ( im,
2292                                X1+7, Y0,
2293                                im->graph_col[GRC_FONT],
2294                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2295                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2296                                graph_label_right );
2297         }
2299         gfx_text(im,
2300                  X0 -
2301                  im->
2302                  text_prop[TEXT_PROP_AXIS].
2303                  size, Y0,
2304                  im->graph_col[GRC_FONT],
2305                  im->
2306                  text_prop[TEXT_PROP_AXIS].
2307                  font_desc,
2308                  im->tabwidth, 0.0,
2309                  GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2310         /* minor grid */
2311         if (mid < 4 && exfrac == 1) {
2312             /* find first and last minor line behind current major line
2313              * i is the first line and j tha last */
2314             if (flab == 0) {
2315                 min_exp = val_exp - 1;
2316                 for (i = 1; yloglab[mid][i] < 10.0; i++);
2317                 i = yloglab[mid][i - 1] + 1;
2318                 j = 10;
2319             } else {
2320                 min_exp = val_exp;
2321                 i = yloglab[mid][flab - 1] + 1;
2322                 j = yloglab[mid][flab];
2323             }
2325             /* draw minor lines below current major line */
2326             for (; i < j; i++) {
2328                 value = i * pow(10.0, min_exp);
2329                 if (value < im->minval)
2330                     continue;
2331                 Y0 = ytr(im, value);
2332                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2333                     break;
2334                 /* draw lines */
2335                 gfx_line(im,
2336                          X0 - 2, Y0,
2337                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2338                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2339                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2340                 gfx_dashed_line(im, X0 - 1, Y0,
2341                                 X1 + 1, Y0,
2342                                 GRIDWIDTH,
2343                                 im->
2344                                 graph_col[GRC_GRID],
2345                                 im->grid_dash_on, im->grid_dash_off);
2346             }
2347         } else if (exfrac > 1) {
2348             for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2349                 value = pow(10.0, i);
2350                 if (value < im->minval)
2351                     continue;
2352                 Y0 = ytr(im, value);
2353                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2354                     break;
2355                 /* draw lines */
2356                 gfx_line(im,
2357                          X0 - 2, Y0,
2358                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2359                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2360                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2361                 gfx_dashed_line(im, X0 - 1, Y0,
2362                                 X1 + 1, Y0,
2363                                 GRIDWIDTH,
2364                                 im->
2365                                 graph_col[GRC_GRID],
2366                                 im->grid_dash_on, im->grid_dash_off);
2367             }
2368         }
2370         /* next decade */
2371         if (yloglab[mid][++flab] == 10.0) {
2372             flab = 0;
2373             val_exp += exfrac;
2374         }
2375     }
2377     /* draw minor lines after highest major line */
2378     if (mid < 4 && exfrac == 1) {
2379         /* find first and last minor line below current major line
2380          * i is the first line and j tha last */
2381         if (flab == 0) {
2382             min_exp = val_exp - 1;
2383             for (i = 1; yloglab[mid][i] < 10.0; i++);
2384             i = yloglab[mid][i - 1] + 1;
2385             j = 10;
2386         } else {
2387             min_exp = val_exp;
2388             i = yloglab[mid][flab - 1] + 1;
2389             j = yloglab[mid][flab];
2390         }
2392         /* draw minor lines below current major line */
2393         for (; i < j; i++) {
2395             value = i * pow(10.0, min_exp);
2396             if (value < im->minval)
2397                 continue;
2398             Y0 = ytr(im, value);
2399             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2400                 break;
2401             /* draw lines */
2402             gfx_line(im,
2403                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2404             gfx_line(im, X1, Y0, X1 + 2, Y0,
2405                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2406             gfx_dashed_line(im, X0 - 1, Y0,
2407                             X1 + 1, Y0,
2408                             GRIDWIDTH,
2409                             im->
2410                             graph_col[GRC_GRID],
2411                             im->grid_dash_on, im->grid_dash_off);
2412         }
2413     }
2414     /* fancy minor gridlines */
2415     else if (exfrac > 1) {
2416         for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2417             value = pow(10.0, i);
2418             if (value < im->minval)
2419                 continue;
2420             Y0 = ytr(im, value);
2421             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2422                 break;
2423             /* draw lines */
2424             gfx_line(im,
2425                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2426             gfx_line(im, X1, Y0, X1 + 2, Y0,
2427                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2428             gfx_dashed_line(im, X0 - 1, Y0,
2429                             X1 + 1, Y0,
2430                             GRIDWIDTH,
2431                             im->
2432                             graph_col[GRC_GRID],
2433                             im->grid_dash_on, im->grid_dash_off);
2434         }
2435     }
2437     return 1;
2441 void vertical_grid(
2442     image_desc_t *im)
2444     int       xlab_sel; /* which sort of label and grid ? */
2445     time_t    ti, tilab, timajor;
2446     long      factor;
2447     char      graph_label[100];
2448     double    X0, Y0, Y1;   /* points for filled graph and more */
2449     struct tm tm;
2451     /* the type of time grid is determined by finding
2452        the number of seconds per pixel in the graph */
2453     if (im->xlab_user.minsec == -1) {
2454         factor = (im->end - im->start) / im->xsize;
2455         xlab_sel = 0;
2456         while (xlab[xlab_sel + 1].minsec !=
2457                -1 && xlab[xlab_sel + 1].minsec <= factor) {
2458             xlab_sel++;
2459         }               /* pick the last one */
2460         while (xlab[xlab_sel - 1].minsec ==
2461                xlab[xlab_sel].minsec
2462                && xlab[xlab_sel].length > (im->end - im->start)) {
2463             xlab_sel--;
2464         }               /* go back to the smallest size */
2465         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2466         im->xlab_user.gridst = xlab[xlab_sel].gridst;
2467         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2468         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2469         im->xlab_user.labtm = xlab[xlab_sel].labtm;
2470         im->xlab_user.labst = xlab[xlab_sel].labst;
2471         im->xlab_user.precis = xlab[xlab_sel].precis;
2472         im->xlab_user.stst = xlab[xlab_sel].stst;
2473     }
2475     /* y coords are the same for every line ... */
2476     Y0 = im->yorigin;
2477     Y1 = im->yorigin - im->ysize;
2478     /* paint the minor grid */
2479     if (!(im->extra_flags & NOMINOR)) {
2480         for (ti = find_first_time(im->start,
2481                                   im->
2482                                   xlab_user.
2483                                   gridtm,
2484                                   im->
2485                                   xlab_user.
2486                                   gridst),
2487              timajor =
2488              find_first_time(im->start,
2489                              im->xlab_user.
2490                              mgridtm,
2491                              im->xlab_user.
2492                              mgridst);
2493              ti < im->end;
2494              ti =
2495              find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2496             ) {
2497             /* are we inside the graph ? */
2498             if (ti < im->start || ti > im->end)
2499                 continue;
2500             while (timajor < ti) {
2501                 timajor = find_next_time(timajor,
2502                                          im->
2503                                          xlab_user.
2504                                          mgridtm, im->xlab_user.mgridst);
2505             }
2506             if (ti == timajor)
2507                 continue;   /* skip as falls on major grid line */
2508             X0 = xtr(im, ti);
2509             gfx_line(im, X0, Y1 - 2, X0, Y1,
2510                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2511             gfx_line(im, X0, Y0, X0, Y0 + 2,
2512                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2513             gfx_dashed_line(im, X0, Y0 + 1, X0,
2514                             Y1 - 1, GRIDWIDTH,
2515                             im->
2516                             graph_col[GRC_GRID],
2517                             im->grid_dash_on, im->grid_dash_off);
2518         }
2519     }
2521     /* paint the major grid */
2522     for (ti = find_first_time(im->start,
2523                               im->
2524                               xlab_user.
2525                               mgridtm,
2526                               im->
2527                               xlab_user.
2528                               mgridst);
2529          ti < im->end;
2530          ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2531         ) {
2532         /* are we inside the graph ? */
2533         if (ti < im->start || ti > im->end)
2534             continue;
2535         X0 = xtr(im, ti);
2536         gfx_line(im, X0, Y1 - 2, X0, Y1,
2537                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2538         gfx_line(im, X0, Y0, X0, Y0 + 3,
2539                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2540         gfx_dashed_line(im, X0, Y0 + 3, X0,
2541                         Y1 - 2, MGRIDWIDTH,
2542                         im->
2543                         graph_col
2544                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2545     }
2546     /* paint the labels below the graph */
2547     for (ti =
2548          find_first_time(im->start -
2549                          im->xlab_user.
2550                          precis / 2,
2551                          im->xlab_user.
2552                          labtm,
2553                          im->xlab_user.
2554                          labst);
2555          ti <=
2556          im->end -
2557          im->xlab_user.precis / 2;
2558          ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2559         ) {
2560         tilab = ti + im->xlab_user.precis / 2;  /* correct time for the label */
2561         /* are we inside the graph ? */
2562         if (tilab < im->start || tilab > im->end)
2563             continue;
2564 #if HAVE_STRFTIME
2565         localtime_r(&tilab, &tm);
2566         strftime(graph_label, 99, im->xlab_user.stst, &tm);
2567 #else
2568 # error "your libc has no strftime I guess we'll abort the exercise here."
2569 #endif
2570         gfx_text(im,
2571                  xtr(im, tilab),
2572                  Y0 + 3,
2573                  im->graph_col[GRC_FONT],
2574                  im->
2575                  text_prop[TEXT_PROP_AXIS].
2576                  font_desc,
2577                  im->tabwidth, 0.0,
2578                  GFX_H_CENTER, GFX_V_TOP, graph_label);
2579     }
2584 void axis_paint(
2585     image_desc_t *im)
2587     /* draw x and y axis */
2588     /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2589        im->xorigin+im->xsize,im->yorigin-im->ysize,
2590        GRIDWIDTH, im->graph_col[GRC_AXIS]);
2592        gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2593        im->xorigin+im->xsize,im->yorigin-im->ysize,
2594        GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2596     gfx_line(im, im->xorigin - 4,
2597              im->yorigin,
2598              im->xorigin + im->xsize +
2599              4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2600     gfx_line(im, im->xorigin,
2601              im->yorigin + 4,
2602              im->xorigin,
2603              im->yorigin - im->ysize -
2604              4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2605     /* arrow for X and Y axis direction */
2606     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 */
2607                  im->graph_col[GRC_ARROW]);
2608     gfx_close_path(im);
2609     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 */
2610                  im->graph_col[GRC_ARROW]);
2611     gfx_close_path(im);
2612     if (im->second_axis_scale != 0){
2613        gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2614                          im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2615                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2616        gfx_new_area ( im,
2617                    im->xorigin+im->xsize-2,  im->yorigin-im->ysize-2,
2618                    im->xorigin+im->xsize+3,  im->yorigin-im->ysize-2,
2619                    im->xorigin+im->xsize,    im->yorigin-im->ysize-7, /* LINEOFFSET */
2620                    im->graph_col[GRC_ARROW]);
2621        gfx_close_path(im);
2622     }
2626 void grid_paint(
2627     image_desc_t *im)
2629     long      i;
2630     int       res = 0;
2631     double    X0, Y0;   /* points for filled graph and more */
2632     struct gfx_color_t water_color;
2634     if (im->draw_3d_border > 0) {
2635             /* draw 3d border */
2636             i = im->draw_3d_border;
2637             gfx_new_area(im, 0, im->yimg,
2638                          i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2639             gfx_add_point(im, im->ximg - i, i);
2640             gfx_add_point(im, im->ximg, 0);
2641             gfx_add_point(im, 0, 0);
2642             gfx_close_path(im);
2643             gfx_new_area(im, i, im->yimg - i,
2644                          im->ximg - i,
2645                          im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2646             gfx_add_point(im, im->ximg, 0);
2647             gfx_add_point(im, im->ximg, im->yimg);
2648             gfx_add_point(im, 0, im->yimg);
2649             gfx_close_path(im);
2650     }
2651     if (im->draw_x_grid == 1)
2652         vertical_grid(im);
2653     if (im->draw_y_grid == 1) {
2654         if (im->logarithmic) {
2655             res = horizontal_log_grid(im);
2656         } else {
2657             res = draw_horizontal_grid(im);
2658         }
2660         /* dont draw horizontal grid if there is no min and max val */
2661         if (!res) {
2662             char     *nodata = "No Data found";
2664             gfx_text(im, im->ximg / 2,
2665                      (2 * im->yorigin -
2666                       im->ysize) / 2,
2667                      im->graph_col[GRC_FONT],
2668                      im->
2669                      text_prop[TEXT_PROP_AXIS].
2670                      font_desc,
2671                      im->tabwidth, 0.0,
2672                      GFX_H_CENTER, GFX_V_CENTER, nodata);
2673         }
2674     }
2676     /* yaxis unit description */
2677     if (im->ylegend[0] != '\0'){
2678         gfx_text(im,
2679                  im->xOriginLegendY+10,
2680                  im->yOriginLegendY,
2681                  im->graph_col[GRC_FONT],
2682                  im->
2683                  text_prop[TEXT_PROP_UNIT].
2684                  font_desc,
2685                  im->tabwidth,
2686                  RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2688     }
2689     if (im->second_axis_legend[0] != '\0'){
2690             gfx_text( im,
2691                   im->xOriginLegendY2+10,
2692                   im->yOriginLegendY2,
2693                   im->graph_col[GRC_FONT],
2694                   im->text_prop[TEXT_PROP_UNIT].font_desc,
2695                   im->tabwidth,
2696                   RRDGRAPH_YLEGEND_ANGLE,
2697                   GFX_H_CENTER, GFX_V_CENTER,
2698                   im->second_axis_legend);
2699     }
2701     /* graph title */
2702     gfx_text(im,
2703              im->xOriginTitle, im->yOriginTitle+6,
2704              im->graph_col[GRC_FONT],
2705              im->
2706              text_prop[TEXT_PROP_TITLE].
2707              font_desc,
2708              im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2709     /* rrdtool 'logo' */
2710     if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2711         water_color = im->graph_col[GRC_FONT];
2712         water_color.alpha = 0.3;
2713         double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2714         gfx_text(im, xpos, 5,
2715                  water_color,
2716                  im->
2717                  text_prop[TEXT_PROP_WATERMARK].
2718                  font_desc, im->tabwidth,
2719                  -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2720     }
2721     /* graph watermark */
2722     if (im->watermark[0] != '\0') {
2723         water_color = im->graph_col[GRC_FONT];
2724         water_color.alpha = 0.3;
2725         gfx_text(im,
2726                  im->ximg / 2, im->yimg - 6,
2727                  water_color,
2728                  im->
2729                  text_prop[TEXT_PROP_WATERMARK].
2730                  font_desc, im->tabwidth, 0,
2731                  GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2732     }
2734     /* graph labels */
2735     if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2736         for (i = 0; i < im->gdes_c; i++) {
2737             if (im->gdes[i].legend[0] == '\0')
2738                 continue;
2739             /* im->gdes[i].leg_y is the bottom of the legend */
2740             X0 = im->xOriginLegend + im->gdes[i].leg_x;
2741             Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2742             gfx_text(im, X0, Y0,
2743                      im->graph_col[GRC_FONT],
2744                      im->
2745                      text_prop
2746                      [TEXT_PROP_LEGEND].font_desc,
2747                      im->tabwidth, 0.0,
2748                      GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2749             /* The legend for GRAPH items starts with "M " to have
2750                enough space for the box */
2751             if (im->gdes[i].gf != GF_PRINT &&
2752                 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2753                 double    boxH, boxV;
2754                 double    X1, Y1;
2756                 boxH = gfx_get_text_width(im, 0,
2757                                           im->
2758                                           text_prop
2759                                           [TEXT_PROP_LEGEND].
2760                                           font_desc,
2761                                           im->tabwidth, "o") * 1.2;
2762                 boxV = boxH;
2763                 /* shift the box up a bit */
2764                 Y0 -= boxV * 0.4;
2766         if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */ 
2767                         cairo_save(im->cr);
2768                         cairo_new_path(im->cr);
2769                         cairo_set_line_width(im->cr, 1.0);
2770                         gfx_line(im,
2771                                 X0, Y0 - boxV / 2,
2772                                 X0 + boxH, Y0 - boxV / 2,
2773                                 1.0, im->gdes[i].col);
2774                         gfx_close_path(im);
2775                 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2776                         cairo_save(im->cr);
2777                         cairo_new_path(im->cr);
2778                         cairo_set_line_width(im->cr, 1.0);
2779                         gfx_line(im,
2780                                 X0 + boxH / 2, Y0,
2781                                 X0 + boxH / 2, Y0 - boxV,
2782                                 1.0, im->gdes[i].col);
2783                         gfx_close_path(im);
2784                 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2785                         cairo_save(im->cr);
2786                         cairo_new_path(im->cr);
2787                         cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2788                         gfx_line(im,
2789                                 X0, Y0,
2790                                 X0 + boxH, Y0 - boxV,
2791                                 im->gdes[i].linewidth, im->gdes[i].col);
2792                         gfx_close_path(im);
2793                 } else {
2794                 /* make sure transparent colors show up the same way as in the graph */
2795                         gfx_new_area(im,
2796                                      X0, Y0 - boxV,
2797                                      X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2798                         gfx_add_point(im, X0 + boxH, Y0 - boxV);
2799                         gfx_close_path(im);
2800                         gfx_new_area(im, X0, Y0 - boxV, X0,
2801                                      Y0, X0 + boxH, Y0, im->gdes[i].col);
2802                         gfx_add_point(im, X0 + boxH, Y0 - boxV);
2803                         gfx_close_path(im);
2804                         cairo_save(im->cr);
2805                         cairo_new_path(im->cr);
2806                         cairo_set_line_width(im->cr, 1.0);
2807                         X1 = X0 + boxH;
2808                         Y1 = Y0 - boxV;
2809                         gfx_line_fit(im, &X0, &Y0);
2810                         gfx_line_fit(im, &X1, &Y1);
2811                         cairo_move_to(im->cr, X0, Y0);
2812                         cairo_line_to(im->cr, X1, Y0);
2813                         cairo_line_to(im->cr, X1, Y1);
2814                         cairo_line_to(im->cr, X0, Y1);
2815                         cairo_close_path(im->cr);
2816                         cairo_set_source_rgba(im->cr,
2817                                               im->graph_col[GRC_FRAME].red,
2818                                               im->graph_col[GRC_FRAME].green,
2819                                               im->graph_col[GRC_FRAME].blue,
2820                                               im->graph_col[GRC_FRAME].alpha);
2821                 }
2822                 if (im->gdes[i].dash) {
2823                     /* make box borders in legend dashed if the graph is dashed */
2824                     double    dashes[] = {
2825                         3.0
2826                     };
2827                     cairo_set_dash(im->cr, dashes, 1, 0.0);
2828                 }
2829                 cairo_stroke(im->cr);
2830                 cairo_restore(im->cr);
2831             }
2832         }
2833     }
2837 /*****************************************************
2838  * lazy check make sure we rely need to create this graph
2839  *****************************************************/
2841 int lazy_check(
2842     image_desc_t *im)
2844     FILE     *fd = NULL;
2845     int       size = 1;
2846     struct stat imgstat;
2848     if (im->lazy == 0)
2849         return 0;       /* no lazy option */
2850     if (strlen(im->graphfile) == 0)
2851         return 0;       /* inmemory option */
2852     if (stat(im->graphfile, &imgstat) != 0)
2853         return 0;       /* can't stat */
2854     /* one pixel in the existing graph is more then what we would
2855        change here ... */
2856     if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2857         return 0;
2858     if ((fd = fopen(im->graphfile, "rb")) == NULL)
2859         return 0;       /* the file does not exist */
2860     switch (im->imgformat) {
2861     case IF_PNG:
2862         size = PngSize(fd, &(im->ximg), &(im->yimg));
2863         break;
2864     default:
2865         size = 1;
2866     }
2867     fclose(fd);
2868     return size;
2872 int graph_size_location(
2873     image_desc_t
2874     *im,
2875     int elements)
2877     /* The actual size of the image to draw is determined from
2878      ** several sources.  The size given on the command line is
2879      ** the graph area but we need more as we have to draw labels
2880      ** and other things outside the graph area. If the option
2881      ** --full-size-mode is selected the size defines the total
2882      ** image size and the size available for the graph is
2883      ** calculated.
2884      */
2886     /** +---+-----------------------------------+
2887      ** | y |...............graph title.........|
2888      ** |   +---+-------------------------------+
2889      ** | a | y |                               |
2890      ** | x |   |                               |
2891      ** | i | a |                               |
2892      ** | s | x |       main graph area         |
2893      ** |   | i |                               |
2894      ** | t | s |                               |
2895      ** | i |   |                               |
2896      ** | t | l |                               |
2897      ** | l | b +-------------------------------+
2898      ** | e | l |       x axis labels           |
2899      ** +---+---+-------------------------------+
2900      ** |....................legends............|
2901      ** +---------------------------------------+
2902      ** |                   watermark           |
2903      ** +---------------------------------------+
2904      */
2906     int       Xvertical = 0, Xvertical2 = 0, Ytitle =
2907         0, Xylabel = 0, Xmain = 0, Ymain =
2908         0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2910     // no legends and no the shall be plotted it's easy
2911     if (im->extra_flags & ONLY_GRAPH) {
2912         im->xorigin = 0;
2913         im->ximg = im->xsize;
2914         im->yimg = im->ysize;
2915         im->yorigin = im->ysize;
2916         ytr(im, DNAN);
2917         return 0;
2918     }
2920     if(im->watermark[0] != '\0') {
2921         Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2922     }
2924     // calculate the width of the left vertical legend
2925     if (im->ylegend[0] != '\0') {
2926         Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2927     }
2929     // calculate the width of the right vertical legend
2930     if (im->second_axis_legend[0] != '\0') {
2931         Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2932     }
2933     else{
2934         Xvertical2 = Xspacing;
2935     }
2937     if (im->title[0] != '\0') {
2938         /* The title is placed "inbetween" two text lines so it
2939          ** automatically has some vertical spacing.  The horizontal
2940          ** spacing is added here, on each side.
2941          */
2942         /* if necessary, reduce the font size of the title until it fits the image width */
2943         Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2944     }
2945     else{
2946         // we have no title; get a little clearing from the top
2947         Ytitle = 1.5 * Yspacing;
2948     }
2950     if (elements) {
2951         if (im->draw_x_grid) {
2952             // calculate the height of the horizontal labelling
2953             Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2954         }
2955         if (im->draw_y_grid || im->forceleftspace) {
2956             // calculate the width of the vertical labelling
2957             Xylabel =
2958                 gfx_get_text_width(im, 0,
2959                                    im->text_prop[TEXT_PROP_AXIS].font_desc,
2960                                    im->tabwidth, "0") * im->unitslength;
2961         }
2962     }
2964     // add some space to the labelling
2965     Xylabel += Xspacing;
2967     /* If the legend is printed besides the graph the width has to be
2968      ** calculated first. Placing the legend north or south of the
2969      ** graph requires the width calculation first, so the legend is
2970      ** skipped for the moment.
2971      */
2972     im->legendheight = 0;
2973     im->legendwidth = 0;
2974     if (!(im->extra_flags & NOLEGEND)) {
2975         if(im->legendposition == WEST || im->legendposition == EAST){
2976             if (leg_place(im, 1) == -1){
2977                 return -1;
2978             }
2979         }
2980     }
2982     if (im->extra_flags & FULL_SIZE_MODE) {
2984         /* The actual size of the image to draw has been determined by the user.
2985          ** The graph area is the space remaining after accounting for the legend,
2986          ** the watermark, the axis labels, and the title.
2987          */
2988         im->ximg = im->xsize;
2989         im->yimg = im->ysize;
2990         Xmain = im->ximg;
2991         Ymain = im->yimg;
2993         /* Now calculate the total size.  Insert some spacing where
2994            desired.  im->xorigin and im->yorigin need to correspond
2995            with the lower left corner of the main graph area or, if
2996            this one is not set, the imaginary box surrounding the
2997            pie chart area. */
2998         /* Initial size calculation for the main graph area */
3000         Xmain -= Xylabel;// + Xspacing;
3001         if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3002             Xmain -= im->legendwidth;// + Xspacing;
3003         }
3004         if (im->second_axis_scale != 0){
3005             Xmain -= Xylabel;
3006         }
3007         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3008             Xmain -= Xspacing;
3009         }
3011         Xmain -= Xvertical + Xvertical2;
3013         /* limit the remaining space to 0 */
3014         if(Xmain < 1){
3015             Xmain = 1;
3016         }
3017         im->xsize = Xmain;
3019         /* Putting the legend north or south, the height can now be calculated */
3020         if (!(im->extra_flags & NOLEGEND)) {
3021             if(im->legendposition == NORTH || im->legendposition == SOUTH){
3022                 im->legendwidth = im->ximg;
3023                 if (leg_place(im, 0) == -1){
3024                     return -1;
3025                 }
3026             }
3027         }
3029         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3030             Ymain -=  Yxlabel + im->legendheight;
3031         }
3032         else{
3033             Ymain -= Yxlabel;
3034         }
3036         /* reserve space for the title *or* some padding above the graph */
3037         Ymain -= Ytitle;
3039             /* reserve space for padding below the graph */
3040         if (im->extra_flags & NOLEGEND) {
3041             Ymain -= Yspacing;
3042         }
3044         if (im->watermark[0] != '\0') {
3045             Ymain -= Ywatermark;
3046         }
3047         /* limit the remaining height to 0 */
3048         if(Ymain < 1){
3049             Ymain = 1;
3050         }
3051         im->ysize = Ymain;
3052     } else {            /* dimension options -width and -height refer to the dimensions of the main graph area */
3054         /* The actual size of the image to draw is determined from
3055          ** several sources.  The size given on the command line is
3056          ** the graph area but we need more as we have to draw labels
3057          ** and other things outside the graph area.
3058          */
3060         if (elements) {
3061             Xmain = im->xsize; // + Xspacing;
3062             Ymain = im->ysize;
3063         }
3065         im->ximg = Xmain + Xylabel;
3066         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3067             im->ximg += Xspacing;
3068         }
3070         if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3071             im->ximg += im->legendwidth;// + Xspacing;
3072         }
3073         if (im->second_axis_scale != 0){
3074             im->ximg += Xylabel;
3075         }
3077         im->ximg += Xvertical + Xvertical2;
3079         if (!(im->extra_flags & NOLEGEND)) {
3080             if(im->legendposition == NORTH || im->legendposition == SOUTH){
3081                 im->legendwidth = im->ximg;
3082                 if (leg_place(im, 0) == -1){
3083                     return -1;
3084                 }
3085             }
3086         }
3088         im->yimg = Ymain + Yxlabel;
3089         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3090              im->yimg += im->legendheight;
3091         }
3093         /* reserve space for the title *or* some padding above the graph */
3094         if (Ytitle) {
3095             im->yimg += Ytitle;
3096         } else {
3097             im->yimg += 1.5 * Yspacing;
3098         }
3099         /* reserve space for padding below the graph */
3100         if (im->extra_flags & NOLEGEND) {
3101             im->yimg += Yspacing;
3102         }
3104         if (im->watermark[0] != '\0') {
3105             im->yimg += Ywatermark;
3106         }
3107     }
3110     /* In case of putting the legend in west or east position the first
3111      ** legend calculation might lead to wrong positions if some items
3112      ** are not aligned on the left hand side (e.g. centered) as the
3113      ** legendwidth wight have been increased after the item was placed.
3114      ** In this case the positions have to be recalculated.
3115      */
3116     if (!(im->extra_flags & NOLEGEND)) {
3117         if(im->legendposition == WEST || im->legendposition == EAST){
3118             if (leg_place(im, 0) == -1){
3119                 return -1;
3120             }
3121         }
3122     }
3124     /* After calculating all dimensions
3125      ** it is now possible to calculate
3126      ** all offsets.
3127      */
3128     switch(im->legendposition){
3129         case NORTH:
3130             im->xOriginTitle   = Xvertical + Xylabel + (im->xsize / 2);
3131             im->yOriginTitle   = 0;
3133             im->xOriginLegend  = 0;
3134             im->yOriginLegend  = Ytitle;
3136             im->xOriginLegendY = 0;
3137             im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3139             im->xorigin        = Xvertical + Xylabel;
3140             im->yorigin        = Ytitle + im->legendheight + Ymain;
3142             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3143             if (im->second_axis_scale != 0){
3144                 im->xOriginLegendY2 += Xylabel;
3145             }
3146             im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3148             break;
3150         case WEST:
3151             im->xOriginTitle   = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3152             im->yOriginTitle   = 0;
3154             im->xOriginLegend  = 0;
3155             im->yOriginLegend  = Ytitle;
3157             im->xOriginLegendY = im->legendwidth;
3158             im->yOriginLegendY = Ytitle + (Ymain / 2);
3160             im->xorigin        = im->legendwidth + Xvertical + Xylabel;
3161             im->yorigin        = Ytitle + Ymain;
3163             im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3164             if (im->second_axis_scale != 0){
3165                 im->xOriginLegendY2 += Xylabel;
3166             }
3167             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3169             break;
3171         case SOUTH:
3172             im->xOriginTitle   = Xvertical + Xylabel + im->xsize / 2;
3173             im->yOriginTitle   = 0;
3175             im->xOriginLegend  = 0;
3176             im->yOriginLegend  = Ytitle + Ymain + Yxlabel;
3178             im->xOriginLegendY = 0;
3179             im->yOriginLegendY = Ytitle + (Ymain / 2);
3181             im->xorigin        = Xvertical + Xylabel;
3182             im->yorigin        = Ytitle + Ymain;
3184             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3185             if (im->second_axis_scale != 0){
3186                 im->xOriginLegendY2 += Xylabel;
3187             }
3188             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3190             break;
3192         case EAST:
3193             im->xOriginTitle   = Xvertical + Xylabel + im->xsize / 2;
3194             im->yOriginTitle   = 0;
3196             im->xOriginLegend  = Xvertical + Xylabel + Xmain + Xvertical2;
3197             if (im->second_axis_scale != 0){
3198                 im->xOriginLegend += Xylabel;
3199             }
3200             im->yOriginLegend  = Ytitle;
3202             im->xOriginLegendY = 0;
3203             im->yOriginLegendY = Ytitle + (Ymain / 2);
3205             im->xorigin        = Xvertical + Xylabel;
3206             im->yorigin        = Ytitle + Ymain;
3208             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3209             if (im->second_axis_scale != 0){
3210                 im->xOriginLegendY2 += Xylabel;
3211             }
3212             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3214             if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3215                 im->xOriginTitle    += Xspacing;
3216                 im->xOriginLegend   += Xspacing;
3217                 im->xOriginLegendY  += Xspacing;
3218                 im->xorigin         += Xspacing;
3219                 im->xOriginLegendY2 += Xspacing;
3220             }
3221             break;
3222     }
3224     xtr(im, 0);
3225     ytr(im, DNAN);
3226     return 0;
3229 static cairo_status_t cairo_output(
3230     void *closure,
3231     const unsigned char
3232     *data,
3233     unsigned int length)
3235     image_desc_t *im = (image_desc_t*)closure;
3237     im->rendered_image =
3238         (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3239     if (im->rendered_image == NULL)
3240         return CAIRO_STATUS_WRITE_ERROR;
3241     memcpy(im->rendered_image + im->rendered_image_size, data, length);
3242     im->rendered_image_size += length;
3243     return CAIRO_STATUS_SUCCESS;
3246 /* draw that picture thing ... */
3247 int graph_paint(
3248     image_desc_t *im)
3250     int       i, ii;
3251     int       lazy = lazy_check(im);
3252     double    areazero = 0.0;
3253     graph_desc_t *lastgdes = NULL;
3254     rrd_infoval_t info;
3256 //    PangoFontMap *font_map = pango_cairo_font_map_get_default();
3258     /* pull the data from the rrd files ... */
3259     if (data_fetch(im) == -1)
3260         return -1;
3261     /* evaluate VDEF and CDEF operations ... */
3262     if (data_calc(im) == -1)
3263         return -1;
3264     /* calculate and PRINT and GPRINT definitions. We have to do it at
3265      * this point because it will affect the length of the legends
3266      * if there are no graph elements (i==0) we stop here ...
3267      * if we are lazy, try to quit ...
3268      */
3269     i = print_calc(im);
3270     if (i < 0)
3271         return -1;
3273     /* if we want and can be lazy ... quit now */
3274     if (i == 0)
3275         return 0;
3277 /**************************************************************
3278  *** Calculating sizes and locations became a bit confusing ***
3279  *** so I moved this into a separate function.              ***
3280  **************************************************************/
3281     if (graph_size_location(im, i) == -1)
3282         return -1;
3284     info.u_cnt = im->xorigin;
3285     grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3286     info.u_cnt = im->yorigin - im->ysize;
3287     grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3288     info.u_cnt = im->xsize;
3289     grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3290     info.u_cnt = im->ysize;
3291     grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3292     info.u_cnt = im->ximg;
3293     grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3294     info.u_cnt = im->yimg;
3295     grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3296     info.u_cnt = im->start;
3297     grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3298     info.u_cnt = im->end;
3299     grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3301     /* if we want and can be lazy ... quit now */
3302     if (lazy)
3303         return 0;
3305     /* get actual drawing data and find min and max values */
3306     if (data_proc(im) == -1)
3307         return -1;
3308     if (!im->logarithmic) {
3309         si_unit(im);
3310     }
3312     /* identify si magnitude Kilo, Mega Giga ? */
3313     if (!im->rigid && !im->logarithmic)
3314         expand_range(im);   /* make sure the upper and lower limit are
3315                                sensible values */
3317     info.u_val = im->minval;
3318     grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3319     info.u_val = im->maxval;
3320     grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3323     if (!calc_horizontal_grid(im))
3324         return -1;
3325     /* reset precalc */
3326     ytr(im, DNAN);
3327 /*   if (im->gridfit)
3328      apply_gridfit(im); */
3329     /* the actual graph is created by going through the individual
3330        graph elements and then drawing them */
3331     cairo_surface_destroy(im->surface);
3332     switch (im->imgformat) {
3333     case IF_PNG:
3334         im->surface =
3335             cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3336                                        im->ximg * im->zoom,
3337                                        im->yimg * im->zoom);
3338         break;
3339     case IF_PDF:
3340         im->gridfit = 0;
3341         im->surface = strlen(im->graphfile)
3342             ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3343                                        im->yimg * im->zoom)
3344             : cairo_pdf_surface_create_for_stream
3345             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3346         break;
3347     case IF_EPS:
3348         im->gridfit = 0;
3349         im->surface = strlen(im->graphfile)
3350             ?
3351             cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3352                                     im->yimg * im->zoom)
3353             : cairo_ps_surface_create_for_stream
3354             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3355         break;
3356     case IF_SVG:
3357         im->gridfit = 0;
3358         im->surface = strlen(im->graphfile)
3359             ?
3360             cairo_svg_surface_create(im->
3361                                      graphfile,
3362                                      im->ximg * im->zoom, im->yimg * im->zoom)
3363             : cairo_svg_surface_create_for_stream
3364             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3365         cairo_svg_surface_restrict_to_version
3366             (im->surface, CAIRO_SVG_VERSION_1_1);
3367         break;
3368     };
3369     cairo_destroy(im->cr);
3370     im->cr = cairo_create(im->surface);
3371     cairo_set_antialias(im->cr, im->graph_antialias);
3372     cairo_scale(im->cr, im->zoom, im->zoom);
3373 //    pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3374     gfx_new_area(im, 0, 0, 0, im->yimg,
3375                  im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3376     gfx_add_point(im, im->ximg, 0);
3377     gfx_close_path(im);
3378     gfx_new_area(im, im->xorigin,
3379                  im->yorigin,
3380                  im->xorigin +
3381                  im->xsize, im->yorigin,
3382                  im->xorigin +
3383                  im->xsize,
3384                  im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3385     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3386     gfx_close_path(im);
3387     cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3388                     im->xsize, im->ysize + 2.0);
3389     cairo_clip(im->cr);
3390     if (im->minval > 0.0)
3391         areazero = im->minval;
3392     if (im->maxval < 0.0)
3393         areazero = im->maxval;
3394     for (i = 0; i < im->gdes_c; i++) {
3395         switch (im->gdes[i].gf) {
3396         case GF_CDEF:
3397         case GF_VDEF:
3398         case GF_DEF:
3399         case GF_PRINT:
3400         case GF_GPRINT:
3401         case GF_COMMENT:
3402         case GF_TEXTALIGN:
3403         case GF_HRULE:
3404         case GF_VRULE:
3405         case GF_XPORT:
3406         case GF_SHIFT:
3407             break;
3408         case GF_TICK:
3409             for (ii = 0; ii < im->xsize; ii++) {
3410                 if (!isnan(im->gdes[i].p_data[ii])
3411                     && im->gdes[i].p_data[ii] != 0.0) {
3412                     if (im->gdes[i].yrule > 0) {
3413                         gfx_line(im,
3414                                  im->xorigin + ii,
3415                                  im->yorigin + 1.0,
3416                                  im->xorigin + ii,
3417                                  im->yorigin -
3418                                  im->gdes[i].yrule *
3419                                  im->ysize, 1.0, im->gdes[i].col);
3420                     } else if (im->gdes[i].yrule < 0) {
3421                         gfx_line(im,
3422                                  im->xorigin + ii,
3423                                  im->yorigin - im->ysize - 1.0,
3424                                  im->xorigin + ii,
3425                                  im->yorigin - im->ysize -
3426                                                 im->gdes[i].
3427                                                 yrule *
3428                                  im->ysize, 1.0, im->gdes[i].col);
3429                     }
3430                 }
3431             }
3432             break;
3433         case GF_LINE:
3434         case GF_AREA:
3435             /* fix data points at oo and -oo */
3436             for (ii = 0; ii < im->xsize; ii++) {
3437                 if (isinf(im->gdes[i].p_data[ii])) {
3438                     if (im->gdes[i].p_data[ii] > 0) {
3439                         im->gdes[i].p_data[ii] = im->maxval;
3440                     } else {
3441                         im->gdes[i].p_data[ii] = im->minval;
3442                     }
3444                 }
3445             }           /* for */
3447             /* *******************************************************
3448                a           ___. (a,t)
3449                |   |    ___
3450                ____|   |   |   |
3451                |       |___|
3452                -------|--t-1--t--------------------------------
3454                if we know the value at time t was a then
3455                we draw a square from t-1 to t with the value a.
3457                ********************************************************* */
3458             if (im->gdes[i].col.alpha != 0.0) {
3459                 /* GF_LINE and friend */
3460                 if (im->gdes[i].gf == GF_LINE) {
3461                     double    last_y = 0.0;
3462                     int       draw_on = 0;
3464                     cairo_save(im->cr);
3465                     cairo_new_path(im->cr);
3466                     cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3467                     if (im->gdes[i].dash) {
3468                         cairo_set_dash(im->cr,
3469                                        im->gdes[i].p_dashes,
3470                                        im->gdes[i].ndash, im->gdes[i].offset);
3471                     }
3473                     for (ii = 1; ii < im->xsize; ii++) {
3474                         if (isnan(im->gdes[i].p_data[ii])
3475                             || (im->slopemode == 1
3476                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3477                             draw_on = 0;
3478                             continue;
3479                         }
3480                         if (draw_on == 0) {
3481                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3482                             if (im->slopemode == 0) {
3483                                 double    x = ii - 1 + im->xorigin;
3484                                 double    y = last_y;
3486                                 gfx_line_fit(im, &x, &y);
3487                                 cairo_move_to(im->cr, x, y);
3488                                 x = ii + im->xorigin;
3489                                 y = last_y;
3490                                 gfx_line_fit(im, &x, &y);
3491                                 cairo_line_to(im->cr, x, y);
3492                             } else {
3493                                 double    x = ii - 1 + im->xorigin;
3494                                 double    y =
3495                                     ytr(im, im->gdes[i].p_data[ii - 1]);
3496                                 gfx_line_fit(im, &x, &y);
3497                                 cairo_move_to(im->cr, x, y);
3498                                 x = ii + im->xorigin;
3499                                 y = last_y;
3500                                 gfx_line_fit(im, &x, &y);
3501                                 cairo_line_to(im->cr, x, y);
3502                             }
3503                             draw_on = 1;
3504                         } else {
3505                             double    x1 = ii + im->xorigin;
3506                             double    y1 = ytr(im, im->gdes[i].p_data[ii]);
3508                             if (im->slopemode == 0
3509                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3510                                 double    x = ii - 1 + im->xorigin;
3511                                 double    y = y1;
3513                                 gfx_line_fit(im, &x, &y);
3514                                 cairo_line_to(im->cr, x, y);
3515                             };
3516                             last_y = y1;
3517                             gfx_line_fit(im, &x1, &y1);
3518                             cairo_line_to(im->cr, x1, y1);
3519                         };
3520                     }
3521                     cairo_set_source_rgba(im->cr,
3522                                           im->gdes[i].
3523                                           col.red,
3524                                           im->gdes[i].
3525                                           col.green,
3526                                           im->gdes[i].
3527                                           col.blue, im->gdes[i].col.alpha);
3528                     cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3529                     cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3530                     cairo_stroke(im->cr);
3531                     cairo_restore(im->cr);
3532                 } else {
3533                     int       idxI = -1;
3534                     double   *foreY =
3535                         (double *) malloc(sizeof(double) * im->xsize * 2);
3536                     double   *foreX =
3537                         (double *) malloc(sizeof(double) * im->xsize * 2);
3538                     double   *backY =
3539                         (double *) malloc(sizeof(double) * im->xsize * 2);
3540                     double   *backX =
3541                         (double *) malloc(sizeof(double) * im->xsize * 2);
3542                     int       drawem = 0;
3544                     for (ii = 0; ii <= im->xsize; ii++) {
3545                         double    ybase, ytop;
3547                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3548                             int       cntI = 1;
3549                             int       lastI = 0;
3551                             while (cntI < idxI
3552                                    &&
3553                                    AlmostEqual2sComplement(foreY
3554                                                            [lastI],
3555                                                            foreY[cntI], 4)
3556                                    &&
3557                                    AlmostEqual2sComplement(foreY
3558                                                            [lastI],
3559                                                            foreY
3560                                                            [cntI + 1], 4)) {
3561                                 cntI++;
3562                             }
3563                             gfx_new_area(im,
3564                                          backX[0], backY[0],
3565                                          foreX[0], foreY[0],
3566                                          foreX[cntI],
3567                                          foreY[cntI], im->gdes[i].col);
3568                             while (cntI < idxI) {
3569                                 lastI = cntI;
3570                                 cntI++;
3571                                 while (cntI < idxI
3572                                        &&
3573                                        AlmostEqual2sComplement(foreY
3574                                                                [lastI],
3575                                                                foreY[cntI], 4)
3576                                        &&
3577                                        AlmostEqual2sComplement(foreY
3578                                                                [lastI],
3579                                                                foreY
3580                                                                [cntI
3581                                                                 + 1], 4)) {
3582                                     cntI++;
3583                                 }
3584                                 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3585                             }
3586                             gfx_add_point(im, backX[idxI], backY[idxI]);
3587                             while (idxI > 1) {
3588                                 lastI = idxI;
3589                                 idxI--;
3590                                 while (idxI > 1
3591                                        &&
3592                                        AlmostEqual2sComplement(backY
3593                                                                [lastI],
3594                                                                backY[idxI], 4)
3595                                        &&
3596                                        AlmostEqual2sComplement(backY
3597                                                                [lastI],
3598                                                                backY
3599                                                                [idxI
3600                                                                 - 1], 4)) {
3601                                     idxI--;
3602                                 }
3603                                 gfx_add_point(im, backX[idxI], backY[idxI]);
3604                             }
3605                             idxI = -1;
3606                             drawem = 0;
3607                             gfx_close_path(im);
3608                         }
3609                         if (drawem != 0) {
3610                             drawem = 0;
3611                             idxI = -1;
3612                         }
3613                         if (ii == im->xsize)
3614                             break;
3615                         if (im->slopemode == 0 && ii == 0) {
3616                             continue;
3617                         }
3618                         if (isnan(im->gdes[i].p_data[ii])) {
3619                             drawem = 1;
3620                             continue;
3621                         }
3622                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3623                         if (lastgdes && im->gdes[i].stack) {
3624                             ybase = ytr(im, lastgdes->p_data[ii]);
3625                         } else {
3626                             ybase = ytr(im, areazero);
3627                         }
3628                         if (ybase == ytop) {
3629                             drawem = 1;
3630                             continue;
3631                         }
3633                         if (ybase > ytop) {
3634                             double    extra = ytop;
3636                             ytop = ybase;
3637                             ybase = extra;
3638                         }
3639                         if (im->slopemode == 0) {
3640                             backY[++idxI] = ybase - 0.2;
3641                             backX[idxI] = ii + im->xorigin - 1;
3642                             foreY[idxI] = ytop + 0.2;
3643                             foreX[idxI] = ii + im->xorigin - 1;
3644                         }
3645                         backY[++idxI] = ybase - 0.2;
3646                         backX[idxI] = ii + im->xorigin;
3647                         foreY[idxI] = ytop + 0.2;
3648                         foreX[idxI] = ii + im->xorigin;
3649                     }
3650                     /* close up any remaining area */
3651                     free(foreY);
3652                     free(foreX);
3653                     free(backY);
3654                     free(backX);
3655                 }       /* else GF_LINE */
3656             }
3657             /* if color != 0x0 */
3658             /* make sure we do not run into trouble when stacking on NaN */
3659             for (ii = 0; ii < im->xsize; ii++) {
3660                 if (isnan(im->gdes[i].p_data[ii])) {
3661                     if (lastgdes && (im->gdes[i].stack)) {
3662                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3663                     } else {
3664                         im->gdes[i].p_data[ii] = areazero;
3665                     }
3666                 }
3667             }
3668             lastgdes = &(im->gdes[i]);
3669             break;
3670         case GF_STACK:
3671             rrd_set_error
3672                 ("STACK should already be turned into LINE or AREA here");
3673             return -1;
3674             break;
3675         }               /* switch */
3676     }
3677     cairo_reset_clip(im->cr);
3679     /* grid_paint also does the text */
3680     if (!(im->extra_flags & ONLY_GRAPH))
3681         grid_paint(im);
3682     if (!(im->extra_flags & ONLY_GRAPH))
3683         axis_paint(im);
3684     /* the RULES are the last thing to paint ... */
3685     for (i = 0; i < im->gdes_c; i++) {
3687         switch (im->gdes[i].gf) {
3688         case GF_HRULE:
3689             if (im->gdes[i].yrule >= im->minval
3690                 && im->gdes[i].yrule <= im->maxval) {
3691                 cairo_save(im->cr);
3692                 if (im->gdes[i].dash) {
3693                     cairo_set_dash(im->cr,
3694                                    im->gdes[i].p_dashes,
3695                                    im->gdes[i].ndash, im->gdes[i].offset);
3696                 }
3697                 gfx_line(im, im->xorigin,
3698                          ytr(im, im->gdes[i].yrule),
3699                          im->xorigin + im->xsize,
3700                          ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3701                 cairo_stroke(im->cr);
3702                 cairo_restore(im->cr);
3703             }
3704             break;
3705         case GF_VRULE:
3706             if (im->gdes[i].xrule >= im->start
3707                 && im->gdes[i].xrule <= im->end) {
3708                 cairo_save(im->cr);
3709                 if (im->gdes[i].dash) {
3710                     cairo_set_dash(im->cr,
3711                                    im->gdes[i].p_dashes,
3712                                    im->gdes[i].ndash, im->gdes[i].offset);
3713                 }
3714                 gfx_line(im,
3715                          xtr(im, im->gdes[i].xrule),
3716                          im->yorigin, xtr(im,
3717                                           im->
3718                                           gdes[i].
3719                                           xrule),
3720                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3721                 cairo_stroke(im->cr);
3722                 cairo_restore(im->cr);
3723             }
3724             break;
3725         default:
3726             break;
3727         }
3728     }
3731     switch (im->imgformat) {
3732     case IF_PNG:
3733     {
3734         cairo_status_t status;
3736         status = strlen(im->graphfile) ?
3737             cairo_surface_write_to_png(im->surface, im->graphfile)
3738             : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3739                                                 im);
3741         if (status != CAIRO_STATUS_SUCCESS) {
3742             rrd_set_error("Could not save png to '%s'", im->graphfile);
3743             return 1;
3744         }
3745         break;
3746     }
3747     default:
3748         if (strlen(im->graphfile)) {
3749             cairo_show_page(im->cr);
3750         } else {
3751             cairo_surface_finish(im->surface);
3752         }
3753         break;
3754     }
3756     return 0;
3760 /*****************************************************
3761  * graph stuff
3762  *****************************************************/
3764 int gdes_alloc(
3765     image_desc_t *im)
3768     im->gdes_c++;
3769     if ((im->gdes = (graph_desc_t *)
3770          rrd_realloc(im->gdes, (im->gdes_c)
3771                      * sizeof(graph_desc_t))) == NULL) {
3772         rrd_set_error("realloc graph_descs");
3773         return -1;
3774     }
3777     im->gdes[im->gdes_c - 1].step = im->step;
3778     im->gdes[im->gdes_c - 1].step_orig = im->step;
3779     im->gdes[im->gdes_c - 1].stack = 0;
3780     im->gdes[im->gdes_c - 1].linewidth = 0;
3781     im->gdes[im->gdes_c - 1].debug = 0;
3782     im->gdes[im->gdes_c - 1].start = im->start;
3783     im->gdes[im->gdes_c - 1].start_orig = im->start;
3784     im->gdes[im->gdes_c - 1].end = im->end;
3785     im->gdes[im->gdes_c - 1].end_orig = im->end;
3786     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3787     im->gdes[im->gdes_c - 1].data = NULL;
3788     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3789     im->gdes[im->gdes_c - 1].data_first = 0;
3790     im->gdes[im->gdes_c - 1].p_data = NULL;
3791     im->gdes[im->gdes_c - 1].rpnp = NULL;
3792     im->gdes[im->gdes_c - 1].p_dashes = NULL;
3793     im->gdes[im->gdes_c - 1].shift = 0.0;
3794     im->gdes[im->gdes_c - 1].dash = 0;
3795     im->gdes[im->gdes_c - 1].ndash = 0;
3796     im->gdes[im->gdes_c - 1].offset = 0;
3797     im->gdes[im->gdes_c - 1].col.red = 0.0;
3798     im->gdes[im->gdes_c - 1].col.green = 0.0;
3799     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3800     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3801     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3802     im->gdes[im->gdes_c - 1].format[0] = '\0';
3803     im->gdes[im->gdes_c - 1].strftm = 0;
3804     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3805     im->gdes[im->gdes_c - 1].ds = -1;
3806     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3807     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3808     im->gdes[im->gdes_c - 1].yrule = DNAN;
3809     im->gdes[im->gdes_c - 1].xrule = 0;
3810     return 0;
3813 /* copies input untill the first unescaped colon is found
3814    or until input ends. backslashes have to be escaped as well */
3815 int scan_for_col(
3816     const char *const input,
3817     int len,
3818     char *const output)
3820     int       inp, outp = 0;
3822     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3823         if (input[inp] == '\\'
3824             && input[inp + 1] != '\0'
3825             && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3826             output[outp++] = input[++inp];
3827         } else {
3828             output[outp++] = input[inp];
3829         }
3830     }
3831     output[outp] = '\0';
3832     return inp;
3835 /* Now just a wrapper around rrd_graph_v */
3836 int rrd_graph(
3837     int argc,
3838     char **argv,
3839     char ***prdata,
3840     int *xsize,
3841     int *ysize,
3842     FILE * stream,
3843     double *ymin,
3844     double *ymax)
3846     int       prlines = 0;
3847     rrd_info_t *grinfo = NULL;
3848     rrd_info_t *walker;
3850     grinfo = rrd_graph_v(argc, argv);
3851     if (grinfo == NULL)
3852         return -1;
3853     walker = grinfo;
3854     (*prdata) = NULL;
3855     while (walker) {
3856         if (strcmp(walker->key, "image_info") == 0) {
3857             prlines++;
3858             if (((*prdata) =
3859                  (char**)rrd_realloc((*prdata),
3860                              (prlines + 1) * sizeof(char *))) == NULL) {
3861                 rrd_set_error("realloc prdata");
3862                 return 0;
3863             }
3864             /* imginfo goes to position 0 in the prdata array */
3865             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3866                                              + 2) * sizeof(char));
3867             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3868             (*prdata)[prlines] = NULL;
3869         }
3870         /* skip anything else */
3871         walker = walker->next;
3872     }
3873     walker = grinfo;
3874     *xsize = 0;
3875     *ysize = 0;
3876     *ymin = 0;
3877     *ymax = 0;
3878     while (walker) {
3879         if (strcmp(walker->key, "image_width") == 0) {
3880             *xsize = walker->value.u_cnt;
3881         } else if (strcmp(walker->key, "image_height") == 0) {
3882             *ysize = walker->value.u_cnt;
3883         } else if (strcmp(walker->key, "value_min") == 0) {
3884             *ymin = walker->value.u_val;
3885         } else if (strcmp(walker->key, "value_max") == 0) {
3886             *ymax = walker->value.u_val;
3887         } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3888             prlines++;
3889             if (((*prdata) =
3890                  (char**)rrd_realloc((*prdata),
3891                              (prlines + 1) * sizeof(char *))) == NULL) {
3892                 rrd_set_error("realloc prdata");
3893                 return 0;
3894             }
3895             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3896                                              + 2) * sizeof(char));
3897             (*prdata)[prlines] = NULL;
3898             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3899         } else if (strcmp(walker->key, "image") == 0) {
3900             if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3901                    (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3902                 rrd_set_error("writing image");
3903                 return 0;
3904             }
3905         }
3906         /* skip anything else */
3907         walker = walker->next;
3908     }
3909     rrd_info_free(grinfo);
3910     return 0;
3914 /* Some surgery done on this function, it became ridiculously big.
3915 ** Things moved:
3916 ** - initializing     now in rrd_graph_init()
3917 ** - options parsing  now in rrd_graph_options()
3918 ** - script parsing   now in rrd_graph_script()
3919 */
3920 rrd_info_t *rrd_graph_v(
3921     int argc,
3922     char **argv)
3924     image_desc_t im;
3925     rrd_info_t *grinfo;
3926     char *old_locale;
3927     rrd_graph_init(&im);
3928     /* a dummy surface so that we can measure text sizes for placements */
3929     old_locale = setlocale(LC_NUMERIC, NULL);
3930     setlocale(LC_NUMERIC, "C");
3931     rrd_graph_options(argc, argv, &im);
3932     if (rrd_test_error()) {
3933         rrd_info_free(im.grinfo);
3934         im_free(&im);
3935         return NULL;
3936     }
3938     if (optind >= argc) {
3939         rrd_info_free(im.grinfo);
3940         im_free(&im);
3941         rrd_set_error("missing filename");
3942         return NULL;
3943     }
3945     if (strlen(argv[optind]) >= MAXPATH) {
3946         rrd_set_error("filename (including path) too long");
3947         rrd_info_free(im.grinfo);
3948         im_free(&im);
3949         return NULL;
3950     }
3952     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3953     im.graphfile[MAXPATH - 1] = '\0';
3955     if (strcmp(im.graphfile, "-") == 0) {
3956         im.graphfile[0] = '\0';
3957     }
3959     rrd_graph_script(argc, argv, &im, 1);
3960     setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
3962     if (rrd_test_error()) {
3963         rrd_info_free(im.grinfo);
3964         im_free(&im);
3965         return NULL;
3966     }
3968     /* Everything is now read and the actual work can start */
3970     if (graph_paint(&im) == -1) {
3971         rrd_info_free(im.grinfo);
3972         im_free(&im);
3973         return NULL;
3974     }
3977     /* The image is generated and needs to be output.
3978      ** Also, if needed, print a line with information about the image.
3979      */
3981     if (im.imginfo) {
3982         rrd_infoval_t info;
3983         char     *path;
3984         char     *filename;
3986         path = strdup(im.graphfile);
3987         filename = basename(path);
3988         info.u_str =
3989             sprintf_alloc(im.imginfo,
3990                           filename,
3991                           (long) (im.zoom *
3992                                   im.ximg), (long) (im.zoom * im.yimg));
3993         grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3994         free(info.u_str);
3995         free(path);
3996     }
3997     if (im.rendered_image) {
3998         rrd_infoval_t img;
4000         img.u_blo.size = im.rendered_image_size;
4001         img.u_blo.ptr = im.rendered_image;
4002         grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
4003     }
4004     grinfo = im.grinfo;
4005     im_free(&im);
4006     return grinfo;
4009 static void
4010 rrd_set_font_desc (
4011     image_desc_t *im,int prop,char *font, double size ){
4012     if (font){
4013         strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4014         im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4015         /* if we already got one, drop it first */
4016         pango_font_description_free(im->text_prop[prop].font_desc);
4017         im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4018     };
4019     if (size > 0){
4020         im->text_prop[prop].size = size;
4021     };
4022     if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4023         pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4024     };
4027 void rrd_graph_init(
4028     image_desc_t
4029     *im)
4031     unsigned int i;
4032     char     *deffont = getenv("RRD_DEFAULT_FONT");
4033     static PangoFontMap *fontmap = NULL;
4034     PangoContext *context;
4036 #ifdef HAVE_TZSET
4037     tzset();
4038 #endif
4040     im->base = 1000;
4041     im->daemon_addr = NULL;
4042     im->draw_x_grid = 1;
4043     im->draw_y_grid = 1;
4044     im->draw_3d_border = 2;
4045     im->dynamic_labels = 0;
4046     im->extra_flags = 0;
4047     im->font_options = cairo_font_options_create();
4048     im->forceleftspace = 0;
4049     im->gdes_c = 0;
4050     im->gdes = NULL;
4051     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4052     im->grid_dash_off = 1;
4053     im->grid_dash_on = 1;
4054     im->gridfit = 1;
4055     im->grinfo = (rrd_info_t *) NULL;
4056     im->grinfo_current = (rrd_info_t *) NULL;
4057     im->imgformat = IF_PNG;
4058     im->imginfo = NULL;
4059     im->lazy = 0;
4060     im->legenddirection = TOP_DOWN;
4061     im->legendheight = 0;
4062     im->legendposition = SOUTH;
4063     im->legendwidth = 0;
4064     im->logarithmic = 0;
4065     im->maxval = DNAN;
4066     im->minval = 0;
4067     im->minval = DNAN;
4068     im->prt_c = 0;
4069     im->rigid = 0;
4070     im->rendered_image_size = 0;
4071     im->rendered_image = NULL;
4072     im->slopemode = 0;
4073     im->step = 0;
4074     im->symbol = ' ';
4075     im->tabwidth = 40.0;
4076     im->title[0] = '\0';
4077     im->unitsexponent = 9999;
4078     im->unitslength = 6;
4079     im->viewfactor = 1.0;
4080     im->watermark[0] = '\0';
4081     im->with_markup = 0;
4082     im->ximg = 0;
4083     im->xlab_user.minsec = -1;
4084     im->xorigin = 0;
4085     im->xOriginLegend = 0;
4086     im->xOriginLegendY = 0;
4087     im->xOriginLegendY2 = 0;
4088     im->xOriginTitle = 0;
4089     im->xsize = 400;
4090     im->ygridstep = DNAN;
4091     im->yimg = 0;
4092     im->ylegend[0] = '\0';
4093     im->second_axis_scale = 0; /* 0 disables it */
4094     im->second_axis_shift = 0; /* no shift by default */
4095     im->second_axis_legend[0] = '\0';
4096     im->second_axis_format[0] = '\0';
4097     im->yorigin = 0;
4098     im->yOriginLegend = 0;
4099     im->yOriginLegendY = 0;
4100     im->yOriginLegendY2 = 0;
4101     im->yOriginTitle = 0;
4102     im->ysize = 100;
4103     im->zoom = 1;
4105     im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4106     im->cr = cairo_create(im->surface);
4108     for (i = 0; i < DIM(text_prop); i++) {
4109         im->text_prop[i].size = -1;
4110         im->text_prop[i].font_desc = NULL;
4111         rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4112     }
4114     if (fontmap == NULL){
4115         fontmap = pango_cairo_font_map_get_default();
4116     }
4118     context =  pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4120     pango_cairo_context_set_resolution(context, 100);
4122     pango_cairo_update_context(im->cr,context);
4124     im->layout = pango_layout_new(context);
4125     g_object_unref (context);
4127 //  im->layout = pango_cairo_create_layout(im->cr);
4130     cairo_font_options_set_hint_style
4131         (im->font_options, CAIRO_HINT_STYLE_FULL);
4132     cairo_font_options_set_hint_metrics
4133         (im->font_options, CAIRO_HINT_METRICS_ON);
4134     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4138     for (i = 0; i < DIM(graph_col); i++)
4139         im->graph_col[i] = graph_col[i];
4145 void rrd_graph_options(
4146     int argc,
4147     char *argv[],
4148     image_desc_t
4149     *im)
4151     int       stroff;
4152     char     *parsetime_error = NULL;
4153     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4154     time_t    start_tmp = 0, end_tmp = 0;
4155     long      long_tmp;
4156     rrd_time_value_t start_tv, end_tv;
4157     long unsigned int color;
4159     /* defines for long options without a short equivalent. should be bytes,
4160        and may not collide with (the ASCII value of) short options */
4161 #define LONGOPT_UNITS_SI 255
4163 /* *INDENT-OFF* */
4164     struct option long_options[] = {
4165         { "alt-autoscale",      no_argument,       0, 'A'},
4166         { "imgformat",          required_argument, 0, 'a'},
4167         { "font-smoothing-threshold", required_argument, 0, 'B'},
4168         { "base",               required_argument, 0, 'b'},
4169         { "color",              required_argument, 0, 'c'},
4170         { "full-size-mode",     no_argument,       0, 'D'},
4171         { "daemon",             required_argument, 0, 'd'},
4172         { "slope-mode",         no_argument,       0, 'E'},
4173         { "end",                required_argument, 0, 'e'},
4174         { "force-rules-legend", no_argument,       0, 'F'},
4175         { "imginfo",            required_argument, 0, 'f'},
4176         { "graph-render-mode",  required_argument, 0, 'G'},
4177         { "no-legend",          no_argument,       0, 'g'},
4178         { "height",             required_argument, 0, 'h'},
4179         { "no-minor",           no_argument,       0, 'I'},
4180         { "interlaced",         no_argument,       0, 'i'},
4181         { "alt-autoscale-min",  no_argument,       0, 'J'},
4182         { "only-graph",         no_argument,       0, 'j'},
4183         { "units-length",       required_argument, 0, 'L'},
4184         { "lower-limit",        required_argument, 0, 'l'},
4185         { "alt-autoscale-max",  no_argument,       0, 'M'},
4186         { "zoom",               required_argument, 0, 'm'},
4187         { "no-gridfit",         no_argument,       0, 'N'},
4188         { "font",               required_argument, 0, 'n'},
4189         { "logarithmic",        no_argument,       0, 'o'},
4190         { "pango-markup",       no_argument,       0, 'P'},
4191         { "font-render-mode",   required_argument, 0, 'R'},
4192         { "rigid",              no_argument,       0, 'r'},
4193         { "step",               required_argument, 0, 'S'},
4194         { "start",              required_argument, 0, 's'},
4195         { "tabwidth",           required_argument, 0, 'T'},
4196         { "title",              required_argument, 0, 't'},
4197         { "upper-limit",        required_argument, 0, 'u'},
4198         { "vertical-label",     required_argument, 0, 'v'},
4199         { "watermark",          required_argument, 0, 'W'},
4200         { "width",              required_argument, 0, 'w'},
4201         { "units-exponent",     required_argument, 0, 'X'},
4202         { "x-grid",             required_argument, 0, 'x'},
4203         { "alt-y-grid",         no_argument,       0, 'Y'},
4204         { "y-grid",             required_argument, 0, 'y'},
4205         { "lazy",               no_argument,       0, 'z'},
4206         { "units",              required_argument, 0, LONGOPT_UNITS_SI},
4207         { "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 */
4208         { "disable-rrdtool-tag",no_argument,       0, 1001},
4209         { "right-axis",         required_argument, 0, 1002},
4210         { "right-axis-label",   required_argument, 0, 1003},
4211         { "right-axis-format",  required_argument, 0, 1004},
4212         { "legend-position",    required_argument, 0, 1005},
4213         { "legend-direction",   required_argument, 0, 1006},
4214         { "border",             required_argument, 0, 1007},
4215         { "grid-dash",          required_argument, 0, 1008},
4216         { "dynamic-labels",     no_argument,       0, 1009},
4217         {  0, 0, 0, 0}
4218 };
4219 /* *INDENT-ON* */
4221     optind = 0;
4222     opterr = 0;         /* initialize getopt */
4223     rrd_parsetime("end-24h", &start_tv);
4224     rrd_parsetime("now", &end_tv);
4225     while (1) {
4226         int       option_index = 0;
4227         int       opt;
4228         int       col_start, col_end;
4230         opt = getopt_long(argc, argv,
4231                           "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",
4232                           long_options, &option_index);
4233         if (opt == EOF)
4234             break;
4235         switch (opt) {
4236         case 'I':
4237             im->extra_flags |= NOMINOR;
4238             break;
4239         case 'Y':
4240             im->extra_flags |= ALTYGRID;
4241             break;
4242         case 'A':
4243             im->extra_flags |= ALTAUTOSCALE;
4244             break;
4245         case 'J':
4246             im->extra_flags |= ALTAUTOSCALE_MIN;
4247             break;
4248         case 'M':
4249             im->extra_flags |= ALTAUTOSCALE_MAX;
4250             break;
4251         case 'j':
4252             im->extra_flags |= ONLY_GRAPH;
4253             break;
4254         case 'g':
4255             im->extra_flags |= NOLEGEND;
4256             break;
4257         case 1005:
4258             if (strcmp(optarg, "north") == 0) {
4259                 im->legendposition = NORTH;
4260             } else if (strcmp(optarg, "west") == 0) {
4261                 im->legendposition = WEST;
4262             } else if (strcmp(optarg, "south") == 0) {
4263                 im->legendposition = SOUTH;
4264             } else if (strcmp(optarg, "east") == 0) {
4265                 im->legendposition = EAST;
4266             } else {
4267                 rrd_set_error("unknown legend-position '%s'", optarg);
4268                 return;
4269             }
4270             break;
4271         case 1006:
4272             if (strcmp(optarg, "topdown") == 0) {
4273                 im->legenddirection = TOP_DOWN;
4274             } else if (strcmp(optarg, "bottomup") == 0) {
4275                 im->legenddirection = BOTTOM_UP;
4276             } else {
4277                 rrd_set_error("unknown legend-position '%s'", optarg);
4278                 return;
4279             }
4280             break;
4281         case 'F':
4282             im->extra_flags |= FORCE_RULES_LEGEND;
4283             break;
4284         case 1001:
4285             im->extra_flags |= NO_RRDTOOL_TAG;
4286             break;
4287         case LONGOPT_UNITS_SI:
4288             if (im->extra_flags & FORCE_UNITS) {
4289                 rrd_set_error("--units can only be used once!");
4290                 return;
4291             }
4292             if (strcmp(optarg, "si") == 0)
4293                 im->extra_flags |= FORCE_UNITS_SI;
4294             else {
4295                 rrd_set_error("invalid argument for --units: %s", optarg);
4296                 return;
4297             }
4298             break;
4299         case 'X':
4300             im->unitsexponent = atoi(optarg);
4301             break;
4302         case 'L':
4303             im->unitslength = atoi(optarg);
4304             im->forceleftspace = 1;
4305             break;
4306         case 'T':
4307             im->tabwidth = atof(optarg);
4308             break;
4309         case 'S':
4310             im->step = atoi(optarg);
4311             break;
4312         case 'N':
4313             im->gridfit = 0;
4314             break;
4315         case 'P':
4316             im->with_markup = 1;
4317             break;
4318         case 's':
4319             if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4320                 rrd_set_error("start time: %s", parsetime_error);
4321                 return;
4322             }
4323             break;
4324         case 'e':
4325             if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4326                 rrd_set_error("end time: %s", parsetime_error);
4327                 return;
4328             }
4329             break;
4330         case 'x':
4331             if (strcmp(optarg, "none") == 0) {
4332                 im->draw_x_grid = 0;
4333                 break;
4334             };
4335             if (sscanf(optarg,
4336                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4337                        scan_gtm,
4338                        &im->xlab_user.gridst,
4339                        scan_mtm,
4340                        &im->xlab_user.mgridst,
4341                        scan_ltm,
4342                        &im->xlab_user.labst,
4343                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4344                 strncpy(im->xlab_form, optarg + stroff,
4345                         sizeof(im->xlab_form) - 1);
4346                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4347                 if ((int)
4348                     (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4349                     rrd_set_error("unknown keyword %s", scan_gtm);
4350                     return;
4351                 } else if ((int)
4352                            (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4353                            == -1) {
4354                     rrd_set_error("unknown keyword %s", scan_mtm);
4355                     return;
4356                 } else if ((int)
4357                            (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4358                     rrd_set_error("unknown keyword %s", scan_ltm);
4359                     return;
4360                 }
4361                 im->xlab_user.minsec = 1;
4362                 im->xlab_user.stst = im->xlab_form;
4363             } else {
4364                 rrd_set_error("invalid x-grid format");
4365                 return;
4366             }
4367             break;
4368         case 'y':
4370             if (strcmp(optarg, "none") == 0) {
4371                 im->draw_y_grid = 0;
4372                 break;
4373             };
4374             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4375                 if (im->ygridstep <= 0) {
4376                     rrd_set_error("grid step must be > 0");
4377                     return;
4378                 } else if (im->ylabfact < 1) {
4379                     rrd_set_error("label factor must be > 0");
4380                     return;
4381                 }
4382             } else {
4383                 rrd_set_error("invalid y-grid format");
4384                 return;
4385             }
4386             break;
4387         case 1007:
4388             im->draw_3d_border = atoi(optarg);
4389             break;
4390         case 1008: /* grid-dash */
4391             if(sscanf(optarg,
4392                       "%lf:%lf",
4393                       &im->grid_dash_on,
4394                       &im->grid_dash_off) != 2) {
4395                 rrd_set_error("expected grid-dash format float:float");
4396                 return;
4397             }
4398             break;   
4399         case 1009: /* enable dynamic labels */
4400             im->dynamic_labels = 1;
4401             break;         
4402         case 1002: /* right y axis */
4404             if(sscanf(optarg,
4405                       "%lf:%lf",
4406                       &im->second_axis_scale,
4407                       &im->second_axis_shift) == 2) {
4408                 if(im->second_axis_scale==0){
4409                     rrd_set_error("the second_axis_scale  must not be 0");
4410                     return;
4411                 }
4412             } else {
4413                 rrd_set_error("invalid right-axis format expected scale:shift");
4414                 return;
4415             }
4416             break;
4417         case 1003:
4418             strncpy(im->second_axis_legend,optarg,150);
4419             im->second_axis_legend[150]='\0';
4420             break;
4421         case 1004:
4422             if (bad_format(optarg)){
4423                 rrd_set_error("use either %le or %lf formats");
4424                 return;
4425             }
4426             strncpy(im->second_axis_format,optarg,150);
4427             im->second_axis_format[150]='\0';
4428             break;
4429         case 'v':
4430             strncpy(im->ylegend, optarg, 150);
4431             im->ylegend[150] = '\0';
4432             break;
4433         case 'u':
4434             im->maxval = atof(optarg);
4435             break;
4436         case 'l':
4437             im->minval = atof(optarg);
4438             break;
4439         case 'b':
4440             im->base = atol(optarg);
4441             if (im->base != 1024 && im->base != 1000) {
4442                 rrd_set_error
4443                     ("the only sensible value for base apart from 1000 is 1024");
4444                 return;
4445             }
4446             break;
4447         case 'w':
4448             long_tmp = atol(optarg);
4449             if (long_tmp < 10) {
4450                 rrd_set_error("width below 10 pixels");
4451                 return;
4452             }
4453             im->xsize = long_tmp;
4454             break;
4455         case 'h':
4456             long_tmp = atol(optarg);
4457             if (long_tmp < 10) {
4458                 rrd_set_error("height below 10 pixels");
4459                 return;
4460             }
4461             im->ysize = long_tmp;
4462             break;
4463         case 'D':
4464             im->extra_flags |= FULL_SIZE_MODE;
4465             break;
4466         case 'i':
4467             /* interlaced png not supported at the moment */
4468             break;
4469         case 'r':
4470             im->rigid = 1;
4471             break;
4472         case 'f':
4473             im->imginfo = optarg;
4474             break;
4475         case 'a':
4476             if ((int)
4477                 (im->imgformat = if_conv(optarg)) == -1) {
4478                 rrd_set_error("unsupported graphics format '%s'", optarg);
4479                 return;
4480             }
4481             break;
4482         case 'z':
4483             im->lazy = 1;
4484             break;
4485         case 'E':
4486             im->slopemode = 1;
4487             break;
4488         case 'o':
4489             im->logarithmic = 1;
4490             break;
4491         case 'c':
4492             if (sscanf(optarg,
4493                        "%10[A-Z]#%n%8lx%n",
4494                        col_nam, &col_start, &color, &col_end) == 2) {
4495                 int       ci;
4496                 int       col_len = col_end - col_start;
4498                 switch (col_len) {
4499                 case 3:
4500                     color =
4501                         (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4502                                                          0x011000) |
4503                          ((color & 0x00F)
4504                           * 0x001100)
4505                          | 0x000000FF);
4506                     break;
4507                 case 4:
4508                     color =
4509                         (((color & 0xF000) *
4510                           0x11000) | ((color & 0x0F00) *
4511                                       0x01100) | ((color &
4512                                                    0x00F0) *
4513                                                   0x00110) |
4514                          ((color & 0x000F) * 0x00011)
4515                         );
4516                     break;
4517                 case 6:
4518                     color = (color << 8) + 0xff /* shift left by 8 */ ;
4519                     break;
4520                 case 8:
4521                     break;
4522                 default:
4523                     rrd_set_error("the color format is #RRGGBB[AA]");
4524                     return;
4525                 }
4526                 if ((ci = grc_conv(col_nam)) != -1) {
4527                     im->graph_col[ci] = gfx_hex_to_col(color);
4528                 } else {
4529                     rrd_set_error("invalid color name '%s'", col_nam);
4530                     return;
4531                 }
4532             } else {
4533                 rrd_set_error("invalid color def format");
4534                 return;
4535             }
4536             break;
4537         case 'n':{
4538             char      prop[15];
4539             double    size = 1;
4540             int       end;
4542             if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4543                 int       sindex, propidx;
4545                 if ((sindex = text_prop_conv(prop)) != -1) {
4546                     for (propidx = sindex;
4547                          propidx < TEXT_PROP_LAST; propidx++) {
4548                         if (size > 0) {
4549                             rrd_set_font_desc(im,propidx,NULL,size);
4550                         }
4551                         if ((int) strlen(optarg) > end+2) {
4552                             if (optarg[end] == ':') {
4553                                 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4554                             } else {
4555                                 rrd_set_error
4556                                     ("expected : after font size in '%s'",
4557                                      optarg);
4558                                 return;
4559                             }
4560                         }
4561                         /* only run the for loop for DEFAULT (0) for
4562                            all others, we break here. woodo programming */
4563                         if (propidx == sindex && sindex != 0)
4564                             break;
4565                     }
4566                 } else {
4567                     rrd_set_error("invalid fonttag '%s'", prop);
4568                     return;
4569                 }
4570             } else {
4571                 rrd_set_error("invalid text property format");
4572                 return;
4573             }
4574             break;
4575         }
4576         case 'm':
4577             im->zoom = atof(optarg);
4578             if (im->zoom <= 0.0) {
4579                 rrd_set_error("zoom factor must be > 0");
4580                 return;
4581             }
4582             break;
4583         case 't':
4584             strncpy(im->title, optarg, 150);
4585             im->title[150] = '\0';
4586             break;
4587         case 'R':
4588             if (strcmp(optarg, "normal") == 0) {
4589                 cairo_font_options_set_antialias
4590                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4591                 cairo_font_options_set_hint_style
4592                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4593             } else if (strcmp(optarg, "light") == 0) {
4594                 cairo_font_options_set_antialias
4595                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4596                 cairo_font_options_set_hint_style
4597                     (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4598             } else if (strcmp(optarg, "mono") == 0) {
4599                 cairo_font_options_set_antialias
4600                     (im->font_options, CAIRO_ANTIALIAS_NONE);
4601                 cairo_font_options_set_hint_style
4602                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4603             } else {
4604                 rrd_set_error("unknown font-render-mode '%s'", optarg);
4605                 return;
4606             }
4607             break;
4608         case 'G':
4609             if (strcmp(optarg, "normal") == 0)
4610                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4611             else if (strcmp(optarg, "mono") == 0)
4612                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4613             else {
4614                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4615                 return;
4616             }
4617             break;
4618         case 'B':
4619             /* not supported curently */
4620             break;
4621         case 'W':
4622             strncpy(im->watermark, optarg, 100);
4623             im->watermark[99] = '\0';
4624             break;
4625         case 'd':
4626         {
4627             if (im->daemon_addr != NULL)
4628             {
4629                 rrd_set_error ("You cannot specify --daemon "
4630                         "more than once.");
4631                 return;
4632             }
4634             im->daemon_addr = strdup(optarg);
4635             if (im->daemon_addr == NULL)
4636             {
4637               rrd_set_error("strdup failed");
4638               return;
4639             }
4641             break;
4642         }
4643         case '?':
4644             if (optopt != 0)
4645                 rrd_set_error("unknown option '%c'", optopt);
4646             else
4647                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4648             return;
4649         }
4650     } /* while (1) */
4652     {   /* try to connect to rrdcached */
4653         int status = rrdc_connect(im->daemon_addr);
4654         if (status != 0) return;
4655     }
4657     pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4658     pango_layout_context_changed(im->layout);
4662     if (im->logarithmic && im->minval <= 0) {
4663         rrd_set_error
4664             ("for a logarithmic yaxis you must specify a lower-limit > 0");
4665         return;
4666     }
4668     if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4669         /* error string is set in rrd_parsetime.c */
4670         return;
4671     }
4673     if (start_tmp < 3600 * 24 * 365 * 10) {
4674         rrd_set_error
4675             ("the first entry to fetch should be after 1980 (%ld)",
4676              start_tmp);
4677         return;
4678     }
4680     if (end_tmp < start_tmp) {
4681         rrd_set_error
4682             ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4683         return;
4684     }
4686     im->start = start_tmp;
4687     im->end = end_tmp;
4688     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4691 int rrd_graph_color(
4692     image_desc_t
4693     *im,
4694     char *var,
4695     char *err,
4696     int optional)
4698     char     *color;
4699     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4701     color = strstr(var, "#");
4702     if (color == NULL) {
4703         if (optional == 0) {
4704             rrd_set_error("Found no color in %s", err);
4705             return 0;
4706         }
4707         return 0;
4708     } else {
4709         int       n = 0;
4710         char     *rest;
4711         long unsigned int col;
4713         rest = strstr(color, ":");
4714         if (rest != NULL)
4715             n = rest - color;
4716         else
4717             n = strlen(color);
4718         switch (n) {
4719         case 7:
4720             sscanf(color, "#%6lx%n", &col, &n);
4721             col = (col << 8) + 0xff /* shift left by 8 */ ;
4722             if (n != 7)
4723                 rrd_set_error("Color problem in %s", err);
4724             break;
4725         case 9:
4726             sscanf(color, "#%8lx%n", &col, &n);
4727             if (n == 9)
4728                 break;
4729         default:
4730             rrd_set_error("Color problem in %s", err);
4731         }
4732         if (rrd_test_error())
4733             return 0;
4734         gdp->col = gfx_hex_to_col(col);
4735         return n;
4736     }
4740 int bad_format(
4741     char *fmt)
4743     char     *ptr;
4744     int       n = 0;
4746     ptr = fmt;
4747     while (*ptr != '\0')
4748         if (*ptr++ == '%') {
4750             /* line cannot end with percent char */
4751             if (*ptr == '\0')
4752                 return 1;
4753             /* '%s', '%S' and '%%' are allowed */
4754             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4755                 ptr++;
4756             /* %c is allowed (but use only with vdef!) */
4757             else if (*ptr == 'c') {
4758                 ptr++;
4759                 n = 1;
4760             }
4762             /* or else '% 6.2lf' and such are allowed */
4763             else {
4764                 /* optional padding character */
4765                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4766                     ptr++;
4767                 /* This should take care of 'm.n' with all three optional */
4768                 while (*ptr >= '0' && *ptr <= '9')
4769                     ptr++;
4770                 if (*ptr == '.')
4771                     ptr++;
4772                 while (*ptr >= '0' && *ptr <= '9')
4773                     ptr++;
4774                 /* Either 'le', 'lf' or 'lg' must follow here */
4775                 if (*ptr++ != 'l')
4776                     return 1;
4777                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4778                     ptr++;
4779                 else
4780                     return 1;
4781                 n++;
4782             }
4783         }
4785     return (n != 1);
4789 int vdef_parse(
4790     struct graph_desc_t
4791     *gdes,
4792     const char *const str)
4794     /* A VDEF currently is either "func" or "param,func"
4795      * so the parsing is rather simple.  Change if needed.
4796      */
4797     double    param;
4798     char      func[30];
4799     int       n;
4801     n = 0;
4802     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4803     if (n == (int) strlen(str)) {   /* matched */
4804         ;
4805     } else {
4806         n = 0;
4807         sscanf(str, "%29[A-Z]%n", func, &n);
4808         if (n == (int) strlen(str)) {   /* matched */
4809             param = DNAN;
4810         } else {
4811             rrd_set_error
4812                 ("Unknown function string '%s' in VDEF '%s'",
4813                  str, gdes->vname);
4814             return -1;
4815         }
4816     }
4817     if (!strcmp("PERCENT", func))
4818         gdes->vf.op = VDEF_PERCENT;
4819     else if (!strcmp("PERCENTNAN", func))
4820         gdes->vf.op = VDEF_PERCENTNAN;
4821     else if (!strcmp("MAXIMUM", func))
4822         gdes->vf.op = VDEF_MAXIMUM;
4823     else if (!strcmp("AVERAGE", func))
4824         gdes->vf.op = VDEF_AVERAGE;
4825     else if (!strcmp("STDEV", func))
4826         gdes->vf.op = VDEF_STDEV;
4827     else if (!strcmp("MINIMUM", func))
4828         gdes->vf.op = VDEF_MINIMUM;
4829     else if (!strcmp("TOTAL", func))
4830         gdes->vf.op = VDEF_TOTAL;
4831     else if (!strcmp("FIRST", func))
4832         gdes->vf.op = VDEF_FIRST;
4833     else if (!strcmp("LAST", func))
4834         gdes->vf.op = VDEF_LAST;
4835     else if (!strcmp("LSLSLOPE", func))
4836         gdes->vf.op = VDEF_LSLSLOPE;
4837     else if (!strcmp("LSLINT", func))
4838         gdes->vf.op = VDEF_LSLINT;
4839     else if (!strcmp("LSLCORREL", func))
4840         gdes->vf.op = VDEF_LSLCORREL;
4841     else {
4842         rrd_set_error
4843             ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4844         return -1;
4845     };
4846     switch (gdes->vf.op) {
4847     case VDEF_PERCENT:
4848     case VDEF_PERCENTNAN:
4849         if (isnan(param)) { /* no parameter given */
4850             rrd_set_error
4851                 ("Function '%s' needs parameter in VDEF '%s'\n",
4852                  func, gdes->vname);
4853             return -1;
4854         };
4855         if (param >= 0.0 && param <= 100.0) {
4856             gdes->vf.param = param;
4857             gdes->vf.val = DNAN;    /* undefined */
4858             gdes->vf.when = 0;  /* undefined */
4859         } else {
4860             rrd_set_error
4861                 ("Parameter '%f' out of range in VDEF '%s'\n",
4862                  param, gdes->vname);
4863             return -1;
4864         };
4865         break;
4866     case VDEF_MAXIMUM:
4867     case VDEF_AVERAGE:
4868     case VDEF_STDEV:
4869     case VDEF_MINIMUM:
4870     case VDEF_TOTAL:
4871     case VDEF_FIRST:
4872     case VDEF_LAST:
4873     case VDEF_LSLSLOPE:
4874     case VDEF_LSLINT:
4875     case VDEF_LSLCORREL:
4876         if (isnan(param)) {
4877             gdes->vf.param = DNAN;
4878             gdes->vf.val = DNAN;
4879             gdes->vf.when = 0;
4880         } else {
4881             rrd_set_error
4882                 ("Function '%s' needs no parameter in VDEF '%s'\n",
4883                  func, gdes->vname);
4884             return -1;
4885         };
4886         break;
4887     };
4888     return 0;
4892 int vdef_calc(
4893     image_desc_t *im,
4894     int gdi)
4896     graph_desc_t *src, *dst;
4897     rrd_value_t *data;
4898     long      step, steps;
4900     dst = &im->gdes[gdi];
4901     src = &im->gdes[dst->vidx];
4902     data = src->data + src->ds;
4904     steps = (src->end - src->start) / src->step;
4905 #if 0
4906     printf
4907         ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4908          src->start, src->end, steps);
4909 #endif
4910     switch (dst->vf.op) {
4911     case VDEF_PERCENT:{
4912         rrd_value_t *array;
4913         int       field;
4914         if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4915             rrd_set_error("malloc VDEV_PERCENT");
4916             return -1;
4917         }
4918         for (step = 0; step < steps; step++) {
4919             array[step] = data[step * src->ds_cnt];
4920         }
4921         qsort(array, step, sizeof(double), vdef_percent_compar);
4922         field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
4923         dst->vf.val = array[field];
4924         dst->vf.when = 0;   /* no time component */
4925         free(array);
4926 #if 0
4927         for (step = 0; step < steps; step++)
4928             printf("DEBUG: %3li:%10.2f %c\n",
4929                    step, array[step], step == field ? '*' : ' ');
4930 #endif
4931     }
4932         break;
4933     case VDEF_PERCENTNAN:{
4934         rrd_value_t *array;
4935         int       field;
4936        /* count number of "valid" values */
4937        int nancount=0;
4938        for (step = 0; step < steps; step++) {
4939          if (!isnan(data[step * src->ds_cnt])) { nancount++; }
4940        }
4941        /* and allocate it */
4942         if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
4943             rrd_set_error("malloc VDEV_PERCENT");
4944             return -1;
4945         }
4946        /* and fill it in */
4947        field=0;
4948         for (step = 0; step < steps; step++) {
4949            if (!isnan(data[step * src->ds_cnt])) {
4950                 array[field] = data[step * src->ds_cnt];
4951                field++;
4952             }
4953         }
4954         qsort(array, nancount, sizeof(double), vdef_percent_compar);
4955         field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
4956         dst->vf.val = array[field];
4957         dst->vf.when = 0;   /* no time component */
4958         free(array);
4959     }
4960         break;
4961     case VDEF_MAXIMUM:
4962         step = 0;
4963         while (step != steps && isnan(data[step * src->ds_cnt]))
4964             step++;
4965         if (step == steps) {
4966             dst->vf.val = DNAN;
4967             dst->vf.when = 0;
4968         } else {
4969             dst->vf.val = data[step * src->ds_cnt];
4970             dst->vf.when = src->start + (step + 1) * src->step;
4971         }
4972         while (step != steps) {
4973             if (finite(data[step * src->ds_cnt])) {
4974                 if (data[step * src->ds_cnt] > dst->vf.val) {
4975                     dst->vf.val = data[step * src->ds_cnt];
4976                     dst->vf.when = src->start + (step + 1) * src->step;
4977                 }
4978             }
4979             step++;
4980         }
4981         break;
4982     case VDEF_TOTAL:
4983     case VDEF_STDEV:
4984     case VDEF_AVERAGE:{
4985         int       cnt = 0;
4986         double    sum = 0.0;
4987         double    average = 0.0;
4989         for (step = 0; step < steps; step++) {
4990             if (finite(data[step * src->ds_cnt])) {
4991                 sum += data[step * src->ds_cnt];
4992                 cnt++;
4993             };
4994         }
4995         if (cnt) {
4996             if (dst->vf.op == VDEF_TOTAL) {
4997                 dst->vf.val = sum * src->step;
4998                 dst->vf.when = 0;   /* no time component */
4999             } else if (dst->vf.op == VDEF_AVERAGE) {
5000                 dst->vf.val = sum / cnt;
5001                 dst->vf.when = 0;   /* no time component */
5002             } else {
5003                 average = sum / cnt;
5004                 sum = 0.0;
5005                 for (step = 0; step < steps; step++) {
5006                     if (finite(data[step * src->ds_cnt])) {
5007                         sum += pow((data[step * src->ds_cnt] - average), 2.0);
5008                     };
5009                 }
5010                 dst->vf.val = pow(sum / cnt, 0.5);
5011                 dst->vf.when = 0;   /* no time component */
5012             };
5013         } else {
5014             dst->vf.val = DNAN;
5015             dst->vf.when = 0;
5016         }
5017     }
5018         break;
5019     case VDEF_MINIMUM:
5020         step = 0;
5021         while (step != steps && isnan(data[step * src->ds_cnt]))
5022             step++;
5023         if (step == steps) {
5024             dst->vf.val = DNAN;
5025             dst->vf.when = 0;
5026         } else {
5027             dst->vf.val = data[step * src->ds_cnt];
5028             dst->vf.when = src->start + (step + 1) * src->step;
5029         }
5030         while (step != steps) {
5031             if (finite(data[step * src->ds_cnt])) {
5032                 if (data[step * src->ds_cnt] < dst->vf.val) {
5033                     dst->vf.val = data[step * src->ds_cnt];
5034                     dst->vf.when = src->start + (step + 1) * src->step;
5035                 }
5036             }
5037             step++;
5038         }
5039         break;
5040     case VDEF_FIRST:
5041         /* The time value returned here is one step before the
5042          * actual time value.  This is the start of the first
5043          * non-NaN interval.
5044          */
5045         step = 0;
5046         while (step != steps && isnan(data[step * src->ds_cnt]))
5047             step++;
5048         if (step == steps) {    /* all entries were NaN */
5049             dst->vf.val = DNAN;
5050             dst->vf.when = 0;
5051         } else {
5052             dst->vf.val = data[step * src->ds_cnt];
5053             dst->vf.when = src->start + step * src->step;
5054         }
5055         break;
5056     case VDEF_LAST:
5057         /* The time value returned here is the
5058          * actual time value.  This is the end of the last
5059          * non-NaN interval.
5060          */
5061         step = steps - 1;
5062         while (step >= 0 && isnan(data[step * src->ds_cnt]))
5063             step--;
5064         if (step < 0) { /* all entries were NaN */
5065             dst->vf.val = DNAN;
5066             dst->vf.when = 0;
5067         } else {
5068             dst->vf.val = data[step * src->ds_cnt];
5069             dst->vf.when = src->start + (step + 1) * src->step;
5070         }
5071         break;
5072     case VDEF_LSLSLOPE:
5073     case VDEF_LSLINT:
5074     case VDEF_LSLCORREL:{
5075         /* Bestfit line by linear least squares method */
5077         int       cnt = 0;
5078         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5080         SUMx = 0;
5081         SUMy = 0;
5082         SUMxy = 0;
5083         SUMxx = 0;
5084         SUMyy = 0;
5085         for (step = 0; step < steps; step++) {
5086             if (finite(data[step * src->ds_cnt])) {
5087                 cnt++;
5088                 SUMx += step;
5089                 SUMxx += step * step;
5090                 SUMxy += step * data[step * src->ds_cnt];
5091                 SUMy += data[step * src->ds_cnt];
5092                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5093             };
5094         }
5096         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5097         y_intercept = (SUMy - slope * SUMx) / cnt;
5098         correl =
5099             (SUMxy -
5100              (SUMx * SUMy) / cnt) /
5101             sqrt((SUMxx -
5102                   (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5103         if (cnt) {
5104             if (dst->vf.op == VDEF_LSLSLOPE) {
5105                 dst->vf.val = slope;
5106                 dst->vf.when = 0;
5107             } else if (dst->vf.op == VDEF_LSLINT) {
5108                 dst->vf.val = y_intercept;
5109                 dst->vf.when = 0;
5110             } else if (dst->vf.op == VDEF_LSLCORREL) {
5111                 dst->vf.val = correl;
5112                 dst->vf.when = 0;
5113             };
5114         } else {
5115             dst->vf.val = DNAN;
5116             dst->vf.when = 0;
5117         }
5118     }
5119         break;
5120     }
5121     return 0;
5124 /* NaN < -INF < finite_values < INF */
5125 int vdef_percent_compar(
5126     const void
5127     *a,
5128     const void
5129     *b)
5131     /* Equality is not returned; this doesn't hurt except
5132      * (maybe) for a little performance.
5133      */
5135     /* First catch NaN values. They are smallest */
5136     if (isnan(*(double *) a))
5137         return -1;
5138     if (isnan(*(double *) b))
5139         return 1;
5140     /* NaN doesn't reach this part so INF and -INF are extremes.
5141      * The sign from isinf() is compatible with the sign we return
5142      */
5143     if (isinf(*(double *) a))
5144         return isinf(*(double *) a);
5145     if (isinf(*(double *) b))
5146         return isinf(*(double *) b);
5147     /* If we reach this, both values must be finite */
5148     if (*(double *) a < *(double *) b)
5149         return -1;
5150     else
5151         return 1;
5154 void grinfo_push(
5155     image_desc_t *im,
5156     char *key,
5157     rrd_info_type_t type,
5158     rrd_infoval_t value)
5160     im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5161     if (im->grinfo == NULL) {
5162         im->grinfo = im->grinfo_current;
5163     }