Code

make sure we do not try to draw points all that far outside the drawing area
[rrdtool-all.git] / program / 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             rrd_value_t diffval = im->maxval - im->minval;
3436             rrd_value_t maxlimit = im->maxval + 9 * diffval;
3437             rrd_value_t minlimit = im->minval - 9 * diffval;        
3438             for (ii = 0; ii < im->xsize; ii++) {
3439                /* fix data points at oo and -oo */
3440                 if (isinf(im->gdes[i].p_data[ii])) {
3441                     if (im->gdes[i].p_data[ii] > 0) {
3442                         im->gdes[i].p_data[ii] = im->maxval;
3443                     } else {
3444                         im->gdes[i].p_data[ii] = im->minval;
3445                     }
3446                 }
3447                 /* some versions of cairo go unstable when trying
3448                    to draw way out of the canvas ... lets not even try */
3449                 if (im->gdes[i].p_data[ii] > maxlimit) {
3450                     im->gdes[i].p_data[ii] = maxlimit;
3451                 }
3452                 if (im->gdes[i].p_data[ii] < minlimit) {
3453                     im->gdes[i].p_data[ii] = minlimit;
3454                 }
3455             }           /* for */
3457             /* *******************************************************
3458                a           ___. (a,t)
3459                |   |    ___
3460                ____|   |   |   |
3461                |       |___|
3462                -------|--t-1--t--------------------------------
3464                if we know the value at time t was a then
3465                we draw a square from t-1 to t with the value a.
3467                ********************************************************* */
3468             if (im->gdes[i].col.alpha != 0.0) {
3469                 /* GF_LINE and friend */
3470                 if (im->gdes[i].gf == GF_LINE) {
3471                     double    last_y = 0.0;
3472                     int       draw_on = 0;
3474                     cairo_save(im->cr);
3475                     cairo_new_path(im->cr);
3476                     cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3477                     if (im->gdes[i].dash) {
3478                         cairo_set_dash(im->cr,
3479                                        im->gdes[i].p_dashes,
3480                                        im->gdes[i].ndash, im->gdes[i].offset);
3481                     }
3483                     for (ii = 1; ii < im->xsize; ii++) {
3484                         if (isnan(im->gdes[i].p_data[ii])
3485                             || (im->slopemode == 1
3486                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3487                             draw_on = 0;
3488                             continue;
3489                         }
3490                         if (draw_on == 0) {
3491                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3492                             if (im->slopemode == 0) {
3493                                 double    x = ii - 1 + im->xorigin;
3494                                 double    y = last_y;
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                             } else {
3503                                 double    x = ii - 1 + im->xorigin;
3504                                 double    y =
3505                                     ytr(im, im->gdes[i].p_data[ii - 1]);
3506                                 gfx_line_fit(im, &x, &y);
3507                                 cairo_move_to(im->cr, x, y);
3508                                 x = ii + im->xorigin;
3509                                 y = last_y;
3510                                 gfx_line_fit(im, &x, &y);
3511                                 cairo_line_to(im->cr, x, y);
3512                             }
3513                             draw_on = 1;
3514                         } else {
3515                             double    x1 = ii + im->xorigin;
3516                             double    y1 = ytr(im, im->gdes[i].p_data[ii]);
3518                             if (im->slopemode == 0
3519                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3520                                 double    x = ii - 1 + im->xorigin;
3521                                 double    y = y1;
3523                                 gfx_line_fit(im, &x, &y);
3524                                 cairo_line_to(im->cr, x, y);
3525                             };
3526                             last_y = y1;
3527                             gfx_line_fit(im, &x1, &y1);
3528                             cairo_line_to(im->cr, x1, y1);
3529                         };
3530                     }
3531                     cairo_set_source_rgba(im->cr,
3532                                           im->gdes[i].
3533                                           col.red,
3534                                           im->gdes[i].
3535                                           col.green,
3536                                           im->gdes[i].
3537                                           col.blue, im->gdes[i].col.alpha);
3538                     cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3539                     cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3540                     cairo_stroke(im->cr);
3541                     cairo_restore(im->cr);
3542                 } else {
3543                     int       idxI = -1;
3544                     double   *foreY =
3545                         (double *) malloc(sizeof(double) * im->xsize * 2);
3546                     double   *foreX =
3547                         (double *) malloc(sizeof(double) * im->xsize * 2);
3548                     double   *backY =
3549                         (double *) malloc(sizeof(double) * im->xsize * 2);
3550                     double   *backX =
3551                         (double *) malloc(sizeof(double) * im->xsize * 2);
3552                     int       drawem = 0;
3554                     for (ii = 0; ii <= im->xsize; ii++) {
3555                         double    ybase, ytop;
3557                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3558                             int       cntI = 1;
3559                             int       lastI = 0;
3561                             while (cntI < idxI
3562                                    &&
3563                                    AlmostEqual2sComplement(foreY
3564                                                            [lastI],
3565                                                            foreY[cntI], 4)
3566                                    &&
3567                                    AlmostEqual2sComplement(foreY
3568                                                            [lastI],
3569                                                            foreY
3570                                                            [cntI + 1], 4)) {
3571                                 cntI++;
3572                             }
3573                             gfx_new_area(im,
3574                                          backX[0], backY[0],
3575                                          foreX[0], foreY[0],
3576                                          foreX[cntI],
3577                                          foreY[cntI], im->gdes[i].col);
3578                             while (cntI < idxI) {
3579                                 lastI = cntI;
3580                                 cntI++;
3581                                 while (cntI < idxI
3582                                        &&
3583                                        AlmostEqual2sComplement(foreY
3584                                                                [lastI],
3585                                                                foreY[cntI], 4)
3586                                        &&
3587                                        AlmostEqual2sComplement(foreY
3588                                                                [lastI],
3589                                                                foreY
3590                                                                [cntI
3591                                                                 + 1], 4)) {
3592                                     cntI++;
3593                                 }
3594                                 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3595                             }
3596                             gfx_add_point(im, backX[idxI], backY[idxI]);
3597                             while (idxI > 1) {
3598                                 lastI = idxI;
3599                                 idxI--;
3600                                 while (idxI > 1
3601                                        &&
3602                                        AlmostEqual2sComplement(backY
3603                                                                [lastI],
3604                                                                backY[idxI], 4)
3605                                        &&
3606                                        AlmostEqual2sComplement(backY
3607                                                                [lastI],
3608                                                                backY
3609                                                                [idxI
3610                                                                 - 1], 4)) {
3611                                     idxI--;
3612                                 }
3613                                 gfx_add_point(im, backX[idxI], backY[idxI]);
3614                             }
3615                             idxI = -1;
3616                             drawem = 0;
3617                             gfx_close_path(im);
3618                         }
3619                         if (drawem != 0) {
3620                             drawem = 0;
3621                             idxI = -1;
3622                         }
3623                         if (ii == im->xsize)
3624                             break;
3625                         if (im->slopemode == 0 && ii == 0) {
3626                             continue;
3627                         }
3628                         if (isnan(im->gdes[i].p_data[ii])) {
3629                             drawem = 1;
3630                             continue;
3631                         }
3632                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3633                         if (lastgdes && im->gdes[i].stack) {
3634                             ybase = ytr(im, lastgdes->p_data[ii]);
3635                         } else {
3636                             ybase = ytr(im, areazero);
3637                         }
3638                         if (ybase == ytop) {
3639                             drawem = 1;
3640                             continue;
3641                         }
3643                         if (ybase > ytop) {
3644                             double    extra = ytop;
3646                             ytop = ybase;
3647                             ybase = extra;
3648                         }
3649                         if (im->slopemode == 0) {
3650                             backY[++idxI] = ybase - 0.2;
3651                             backX[idxI] = ii + im->xorigin - 1;
3652                             foreY[idxI] = ytop + 0.2;
3653                             foreX[idxI] = ii + im->xorigin - 1;
3654                         }
3655                         backY[++idxI] = ybase - 0.2;
3656                         backX[idxI] = ii + im->xorigin;
3657                         foreY[idxI] = ytop + 0.2;
3658                         foreX[idxI] = ii + im->xorigin;
3659                     }
3660                     /* close up any remaining area */
3661                     free(foreY);
3662                     free(foreX);
3663                     free(backY);
3664                     free(backX);
3665                 }       /* else GF_LINE */
3666             }
3667             /* if color != 0x0 */
3668             /* make sure we do not run into trouble when stacking on NaN */
3669             for (ii = 0; ii < im->xsize; ii++) {
3670                 if (isnan(im->gdes[i].p_data[ii])) {
3671                     if (lastgdes && (im->gdes[i].stack)) {
3672                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3673                     } else {
3674                         im->gdes[i].p_data[ii] = areazero;
3675                     }
3676                 }
3677             }
3678             lastgdes = &(im->gdes[i]);
3679             break;
3680         } /* GF_AREA, GF_LINE, GF_GRAD */
3681         case GF_STACK:
3682             rrd_set_error
3683                 ("STACK should already be turned into LINE or AREA here");
3684             return -1;
3685             break;
3686         }               /* switch */
3687     }
3688     cairo_reset_clip(im->cr);
3690     /* grid_paint also does the text */
3691     if (!(im->extra_flags & ONLY_GRAPH))
3692         grid_paint(im);
3693     if (!(im->extra_flags & ONLY_GRAPH))
3694         axis_paint(im);
3695     /* the RULES are the last thing to paint ... */
3696     for (i = 0; i < im->gdes_c; i++) {
3698         switch (im->gdes[i].gf) {
3699         case GF_HRULE:
3700             if (im->gdes[i].yrule >= im->minval
3701                 && im->gdes[i].yrule <= im->maxval) {
3702                 cairo_save(im->cr);
3703                 if (im->gdes[i].dash) {
3704                     cairo_set_dash(im->cr,
3705                                    im->gdes[i].p_dashes,
3706                                    im->gdes[i].ndash, im->gdes[i].offset);
3707                 }
3708                 gfx_line(im, im->xorigin,
3709                          ytr(im, im->gdes[i].yrule),
3710                          im->xorigin + im->xsize,
3711                          ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3712                 cairo_stroke(im->cr);
3713                 cairo_restore(im->cr);
3714             }
3715             break;
3716         case GF_VRULE:
3717             if (im->gdes[i].xrule >= im->start
3718                 && im->gdes[i].xrule <= im->end) {
3719                 cairo_save(im->cr);
3720                 if (im->gdes[i].dash) {
3721                     cairo_set_dash(im->cr,
3722                                    im->gdes[i].p_dashes,
3723                                    im->gdes[i].ndash, im->gdes[i].offset);
3724                 }
3725                 gfx_line(im,
3726                          xtr(im, im->gdes[i].xrule),
3727                          im->yorigin, xtr(im,
3728                                           im->
3729                                           gdes[i].
3730                                           xrule),
3731                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3732                 cairo_stroke(im->cr);
3733                 cairo_restore(im->cr);
3734             }
3735             break;
3736         default:
3737             break;
3738         }
3739     }
3742     switch (im->imgformat) {
3743     case IF_PNG:
3744     {
3745         cairo_status_t status;
3747         status = strlen(im->graphfile) ?
3748             cairo_surface_write_to_png(im->surface, im->graphfile)
3749             : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3750                                                 im);
3752         if (status != CAIRO_STATUS_SUCCESS) {
3753             rrd_set_error("Could not save png to '%s'", im->graphfile);
3754             return 1;
3755         }
3756         break;
3757     }
3758     default:
3759         if (strlen(im->graphfile)) {
3760             cairo_show_page(im->cr);
3761         } else {
3762             cairo_surface_finish(im->surface);
3763         }
3764         break;
3765     }
3767     return 0;
3771 /*****************************************************
3772  * graph stuff
3773  *****************************************************/
3775 int gdes_alloc(
3776     image_desc_t *im)
3779     im->gdes_c++;
3780     if ((im->gdes = (graph_desc_t *)
3781          rrd_realloc(im->gdes, (im->gdes_c)
3782                      * sizeof(graph_desc_t))) == NULL) {
3783         rrd_set_error("realloc graph_descs");
3784         return -1;
3785     }
3788     im->gdes[im->gdes_c - 1].step = im->step;
3789     im->gdes[im->gdes_c - 1].step_orig = im->step;
3790     im->gdes[im->gdes_c - 1].stack = 0;
3791     im->gdes[im->gdes_c - 1].linewidth = 0;
3792     im->gdes[im->gdes_c - 1].debug = 0;
3793     im->gdes[im->gdes_c - 1].start = im->start;
3794     im->gdes[im->gdes_c - 1].start_orig = im->start;
3795     im->gdes[im->gdes_c - 1].end = im->end;
3796     im->gdes[im->gdes_c - 1].end_orig = im->end;
3797     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3798     im->gdes[im->gdes_c - 1].data = NULL;
3799     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3800     im->gdes[im->gdes_c - 1].data_first = 0;
3801     im->gdes[im->gdes_c - 1].p_data = NULL;
3802     im->gdes[im->gdes_c - 1].rpnp = NULL;
3803     im->gdes[im->gdes_c - 1].p_dashes = NULL;
3804     im->gdes[im->gdes_c - 1].shift = 0.0;
3805     im->gdes[im->gdes_c - 1].dash = 0;
3806     im->gdes[im->gdes_c - 1].ndash = 0;
3807     im->gdes[im->gdes_c - 1].offset = 0;
3808     im->gdes[im->gdes_c - 1].col.red = 0.0;
3809     im->gdes[im->gdes_c - 1].col.green = 0.0;
3810     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3811     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3812     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3813     im->gdes[im->gdes_c - 1].format[0] = '\0';
3814     im->gdes[im->gdes_c - 1].strftm = 0;
3815     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3816     im->gdes[im->gdes_c - 1].ds = -1;
3817     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3818     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3819     im->gdes[im->gdes_c - 1].yrule = DNAN;
3820     im->gdes[im->gdes_c - 1].xrule = 0;
3821     return 0;
3824 /* copies input untill the first unescaped colon is found
3825    or until input ends. backslashes have to be escaped as well */
3826 int scan_for_col(
3827     const char *const input,
3828     int len,
3829     char *const output)
3831     int       inp, outp = 0;
3833     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3834         if (input[inp] == '\\'
3835             && input[inp + 1] != '\0'
3836             && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3837             output[outp++] = input[++inp];
3838         } else {
3839             output[outp++] = input[inp];
3840         }
3841     }
3842     output[outp] = '\0';
3843     return inp;
3846 /* Now just a wrapper around rrd_graph_v */
3847 int rrd_graph(
3848     int argc,
3849     char **argv,
3850     char ***prdata,
3851     int *xsize,
3852     int *ysize,
3853     FILE * stream,
3854     double *ymin,
3855     double *ymax)
3857     int       prlines = 0;
3858     rrd_info_t *grinfo = NULL;
3859     rrd_info_t *walker;
3861     grinfo = rrd_graph_v(argc, argv);
3862     if (grinfo == NULL)
3863         return -1;
3864     walker = grinfo;
3865     (*prdata) = NULL;
3866     while (walker) {
3867         if (strcmp(walker->key, "image_info") == 0) {
3868             prlines++;
3869             if (((*prdata) =
3870                  (char**)rrd_realloc((*prdata),
3871                              (prlines + 1) * sizeof(char *))) == NULL) {
3872                 rrd_set_error("realloc prdata");
3873                 return 0;
3874             }
3875             /* imginfo goes to position 0 in the prdata array */
3876             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3877                                              + 2) * sizeof(char));
3878             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3879             (*prdata)[prlines] = NULL;
3880         }
3881         /* skip anything else */
3882         walker = walker->next;
3883     }
3884     walker = grinfo;
3885     *xsize = 0;
3886     *ysize = 0;
3887     *ymin = 0;
3888     *ymax = 0;
3889     while (walker) {
3890         if (strcmp(walker->key, "image_width") == 0) {
3891             *xsize = walker->value.u_cnt;
3892         } else if (strcmp(walker->key, "image_height") == 0) {
3893             *ysize = walker->value.u_cnt;
3894         } else if (strcmp(walker->key, "value_min") == 0) {
3895             *ymin = walker->value.u_val;
3896         } else if (strcmp(walker->key, "value_max") == 0) {
3897             *ymax = walker->value.u_val;
3898         } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3899             prlines++;
3900             if (((*prdata) =
3901                  (char**)rrd_realloc((*prdata),
3902                              (prlines + 1) * sizeof(char *))) == NULL) {
3903                 rrd_set_error("realloc prdata");
3904                 return 0;
3905             }
3906             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3907                                              + 2) * sizeof(char));
3908             (*prdata)[prlines] = NULL;
3909             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3910         } else if (strcmp(walker->key, "image") == 0) {
3911             if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3912                    (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3913                 rrd_set_error("writing image");
3914                 return 0;
3915             }
3916         }
3917         /* skip anything else */
3918         walker = walker->next;
3919     }
3920     rrd_info_free(grinfo);
3921     return 0;
3925 /* Some surgery done on this function, it became ridiculously big.
3926 ** Things moved:
3927 ** - initializing     now in rrd_graph_init()
3928 ** - options parsing  now in rrd_graph_options()
3929 ** - script parsing   now in rrd_graph_script()
3930 */
3931 rrd_info_t *rrd_graph_v(
3932     int argc,
3933     char **argv)
3935     image_desc_t im;
3936     rrd_info_t *grinfo;
3937     char *old_locale;
3938     rrd_graph_init(&im);
3939     /* a dummy surface so that we can measure text sizes for placements */
3940     old_locale = setlocale(LC_NUMERIC, NULL);
3941     setlocale(LC_NUMERIC, "C");
3942     rrd_graph_options(argc, argv, &im);
3943     if (rrd_test_error()) {
3944         rrd_info_free(im.grinfo);
3945         im_free(&im);
3946         return NULL;
3947     }
3949     if (optind >= argc) {
3950         rrd_info_free(im.grinfo);
3951         im_free(&im);
3952         rrd_set_error("missing filename");
3953         return NULL;
3954     }
3956     if (strlen(argv[optind]) >= MAXPATH) {
3957         rrd_set_error("filename (including path) too long");
3958         rrd_info_free(im.grinfo);
3959         im_free(&im);
3960         return NULL;
3961     }
3963     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3964     im.graphfile[MAXPATH - 1] = '\0';
3966     if (strcmp(im.graphfile, "-") == 0) {
3967         im.graphfile[0] = '\0';
3968     }
3970     rrd_graph_script(argc, argv, &im, 1);
3971     setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
3973     if (rrd_test_error()) {
3974         rrd_info_free(im.grinfo);
3975         im_free(&im);
3976         return NULL;
3977     }
3979     /* Everything is now read and the actual work can start */
3981     if (graph_paint(&im) == -1) {
3982         rrd_info_free(im.grinfo);
3983         im_free(&im);
3984         return NULL;
3985     }
3988     /* The image is generated and needs to be output.
3989      ** Also, if needed, print a line with information about the image.
3990      */
3992     if (im.imginfo) {
3993         rrd_infoval_t info;
3994         char     *path;
3995         char     *filename;
3997         path = strdup(im.graphfile);
3998         filename = basename(path);
3999         info.u_str =
4000             sprintf_alloc(im.imginfo,
4001                           filename,
4002                           (long) (im.zoom *
4003                                   im.ximg), (long) (im.zoom * im.yimg));
4004         grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
4005         free(info.u_str);
4006         free(path);
4007     }
4008     if (im.rendered_image) {
4009         rrd_infoval_t img;
4011         img.u_blo.size = im.rendered_image_size;
4012         img.u_blo.ptr = im.rendered_image;
4013         grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
4014     }
4015     grinfo = im.grinfo;
4016     im_free(&im);
4017     return grinfo;
4020 static void
4021 rrd_set_font_desc (
4022     image_desc_t *im,int prop,char *font, double size ){
4023     if (font){
4024         strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4025         im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4026         /* if we already got one, drop it first */
4027         pango_font_description_free(im->text_prop[prop].font_desc);
4028         im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4029     };
4030     if (size > 0){
4031         im->text_prop[prop].size = size;
4032     };
4033     if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4034         pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4035     };
4038 void rrd_graph_init(
4039     image_desc_t
4040     *im)
4042     unsigned int i;
4043     char     *deffont = getenv("RRD_DEFAULT_FONT");
4044     static PangoFontMap *fontmap = NULL;
4045     PangoContext *context;
4047 #ifdef HAVE_TZSET
4048     tzset();
4049 #endif
4051     im->base = 1000;
4052     im->daemon_addr = NULL;
4053     im->draw_x_grid = 1;
4054     im->draw_y_grid = 1;
4055     im->draw_3d_border = 2;
4056     im->dynamic_labels = 0;
4057     im->extra_flags = 0;
4058     im->font_options = cairo_font_options_create();
4059     im->forceleftspace = 0;
4060     im->gdes_c = 0;
4061     im->gdes = NULL;
4062     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4063     im->grid_dash_off = 1;
4064     im->grid_dash_on = 1;
4065     im->gridfit = 1;
4066     im->grinfo = (rrd_info_t *) NULL;
4067     im->grinfo_current = (rrd_info_t *) NULL;
4068     im->imgformat = IF_PNG;
4069     im->imginfo = NULL;
4070     im->lazy = 0;
4071     im->legenddirection = TOP_DOWN;
4072     im->legendheight = 0;
4073     im->legendposition = SOUTH;
4074     im->legendwidth = 0;
4075     im->logarithmic = 0;
4076     im->maxval = DNAN;
4077     im->minval = 0;
4078     im->minval = DNAN;
4079     im->prt_c = 0;
4080     im->rigid = 0;
4081     im->rendered_image_size = 0;
4082     im->rendered_image = NULL;
4083     im->slopemode = 0;
4084     im->step = 0;
4085     im->symbol = ' ';
4086     im->tabwidth = 40.0;
4087     im->title[0] = '\0';
4088     im->unitsexponent = 9999;
4089     im->unitslength = 6;
4090     im->viewfactor = 1.0;
4091     im->watermark[0] = '\0';
4092     im->with_markup = 0;
4093     im->ximg = 0;
4094     im->xlab_user.minsec = -1;
4095     im->xorigin = 0;
4096     im->xOriginLegend = 0;
4097     im->xOriginLegendY = 0;
4098     im->xOriginLegendY2 = 0;
4099     im->xOriginTitle = 0;
4100     im->xsize = 400;
4101     im->ygridstep = DNAN;
4102     im->yimg = 0;
4103     im->ylegend[0] = '\0';
4104     im->second_axis_scale = 0; /* 0 disables it */
4105     im->second_axis_shift = 0; /* no shift by default */
4106     im->second_axis_legend[0] = '\0';
4107     im->second_axis_format[0] = '\0';
4108     im->yorigin = 0;
4109     im->yOriginLegend = 0;
4110     im->yOriginLegendY = 0;
4111     im->yOriginLegendY2 = 0;
4112     im->yOriginTitle = 0;
4113     im->ysize = 100;
4114     im->zoom = 1;
4116     im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4117     im->cr = cairo_create(im->surface);
4119     for (i = 0; i < DIM(text_prop); i++) {
4120         im->text_prop[i].size = -1;
4121         im->text_prop[i].font_desc = NULL;
4122         rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4123     }
4125     if (fontmap == NULL){
4126         fontmap = pango_cairo_font_map_get_default();
4127     }
4129     context =  pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4131     pango_cairo_context_set_resolution(context, 100);
4133     pango_cairo_update_context(im->cr,context);
4135     im->layout = pango_layout_new(context);
4136     g_object_unref (context);
4138 //  im->layout = pango_cairo_create_layout(im->cr);
4141     cairo_font_options_set_hint_style
4142         (im->font_options, CAIRO_HINT_STYLE_FULL);
4143     cairo_font_options_set_hint_metrics
4144         (im->font_options, CAIRO_HINT_METRICS_ON);
4145     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4149     for (i = 0; i < DIM(graph_col); i++)
4150         im->graph_col[i] = graph_col[i];
4156 void rrd_graph_options(
4157     int argc,
4158     char *argv[],
4159     image_desc_t
4160     *im)
4162     int       stroff;
4163     char     *parsetime_error = NULL;
4164     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4165     time_t    start_tmp = 0, end_tmp = 0;
4166     long      long_tmp;
4167     rrd_time_value_t start_tv, end_tv;
4168     long unsigned int color;
4170     /* defines for long options without a short equivalent. should be bytes,
4171        and may not collide with (the ASCII value of) short options */
4172 #define LONGOPT_UNITS_SI 255
4174 /* *INDENT-OFF* */
4175     struct option long_options[] = {
4176         { "alt-autoscale",      no_argument,       0, 'A'},
4177         { "imgformat",          required_argument, 0, 'a'},
4178         { "font-smoothing-threshold", required_argument, 0, 'B'},
4179         { "base",               required_argument, 0, 'b'},
4180         { "color",              required_argument, 0, 'c'},
4181         { "full-size-mode",     no_argument,       0, 'D'},
4182         { "daemon",             required_argument, 0, 'd'},
4183         { "slope-mode",         no_argument,       0, 'E'},
4184         { "end",                required_argument, 0, 'e'},
4185         { "force-rules-legend", no_argument,       0, 'F'},
4186         { "imginfo",            required_argument, 0, 'f'},
4187         { "graph-render-mode",  required_argument, 0, 'G'},
4188         { "no-legend",          no_argument,       0, 'g'},
4189         { "height",             required_argument, 0, 'h'},
4190         { "no-minor",           no_argument,       0, 'I'},
4191         { "interlaced",         no_argument,       0, 'i'},
4192         { "alt-autoscale-min",  no_argument,       0, 'J'},
4193         { "only-graph",         no_argument,       0, 'j'},
4194         { "units-length",       required_argument, 0, 'L'},
4195         { "lower-limit",        required_argument, 0, 'l'},
4196         { "alt-autoscale-max",  no_argument,       0, 'M'},
4197         { "zoom",               required_argument, 0, 'm'},
4198         { "no-gridfit",         no_argument,       0, 'N'},
4199         { "font",               required_argument, 0, 'n'},
4200         { "logarithmic",        no_argument,       0, 'o'},
4201         { "pango-markup",       no_argument,       0, 'P'},
4202         { "font-render-mode",   required_argument, 0, 'R'},
4203         { "rigid",              no_argument,       0, 'r'},
4204         { "step",               required_argument, 0, 'S'},
4205         { "start",              required_argument, 0, 's'},
4206         { "tabwidth",           required_argument, 0, 'T'},
4207         { "title",              required_argument, 0, 't'},
4208         { "upper-limit",        required_argument, 0, 'u'},
4209         { "vertical-label",     required_argument, 0, 'v'},
4210         { "watermark",          required_argument, 0, 'W'},
4211         { "width",              required_argument, 0, 'w'},
4212         { "units-exponent",     required_argument, 0, 'X'},
4213         { "x-grid",             required_argument, 0, 'x'},
4214         { "alt-y-grid",         no_argument,       0, 'Y'},
4215         { "y-grid",             required_argument, 0, 'y'},
4216         { "lazy",               no_argument,       0, 'z'},
4217         { "units",              required_argument, 0, LONGOPT_UNITS_SI},
4218         { "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 */
4219         { "disable-rrdtool-tag",no_argument,       0, 1001},
4220         { "right-axis",         required_argument, 0, 1002},
4221         { "right-axis-label",   required_argument, 0, 1003},
4222         { "right-axis-format",  required_argument, 0, 1004},
4223         { "legend-position",    required_argument, 0, 1005},
4224         { "legend-direction",   required_argument, 0, 1006},
4225         { "border",             required_argument, 0, 1007},
4226         { "grid-dash",          required_argument, 0, 1008},
4227         { "dynamic-labels",     no_argument,       0, 1009},
4228         {  0, 0, 0, 0}
4229 };
4230 /* *INDENT-ON* */
4232     optind = 0;
4233     opterr = 0;         /* initialize getopt */
4234     rrd_parsetime("end-24h", &start_tv);
4235     rrd_parsetime("now", &end_tv);
4236     while (1) {
4237         int       option_index = 0;
4238         int       opt;
4239         int       col_start, col_end;
4241         opt = getopt_long(argc, argv,
4242                           "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",
4243                           long_options, &option_index);
4244         if (opt == EOF)
4245             break;
4246         switch (opt) {
4247         case 'I':
4248             im->extra_flags |= NOMINOR;
4249             break;
4250         case 'Y':
4251             im->extra_flags |= ALTYGRID;
4252             break;
4253         case 'A':
4254             im->extra_flags |= ALTAUTOSCALE;
4255             break;
4256         case 'J':
4257             im->extra_flags |= ALTAUTOSCALE_MIN;
4258             break;
4259         case 'M':
4260             im->extra_flags |= ALTAUTOSCALE_MAX;
4261             break;
4262         case 'j':
4263             im->extra_flags |= ONLY_GRAPH;
4264             break;
4265         case 'g':
4266             im->extra_flags |= NOLEGEND;
4267             break;
4268         case 1005:
4269             if (strcmp(optarg, "north") == 0) {
4270                 im->legendposition = NORTH;
4271             } else if (strcmp(optarg, "west") == 0) {
4272                 im->legendposition = WEST;
4273             } else if (strcmp(optarg, "south") == 0) {
4274                 im->legendposition = SOUTH;
4275             } else if (strcmp(optarg, "east") == 0) {
4276                 im->legendposition = EAST;
4277             } else {
4278                 rrd_set_error("unknown legend-position '%s'", optarg);
4279                 return;
4280             }
4281             break;
4282         case 1006:
4283             if (strcmp(optarg, "topdown") == 0) {
4284                 im->legenddirection = TOP_DOWN;
4285             } else if (strcmp(optarg, "bottomup") == 0) {
4286                 im->legenddirection = BOTTOM_UP;
4287             } else {
4288                 rrd_set_error("unknown legend-position '%s'", optarg);
4289                 return;
4290             }
4291             break;
4292         case 'F':
4293             im->extra_flags |= FORCE_RULES_LEGEND;
4294             break;
4295         case 1001:
4296             im->extra_flags |= NO_RRDTOOL_TAG;
4297             break;
4298         case LONGOPT_UNITS_SI:
4299             if (im->extra_flags & FORCE_UNITS) {
4300                 rrd_set_error("--units can only be used once!");
4301                 return;
4302             }
4303             if (strcmp(optarg, "si") == 0)
4304                 im->extra_flags |= FORCE_UNITS_SI;
4305             else {
4306                 rrd_set_error("invalid argument for --units: %s", optarg);
4307                 return;
4308             }
4309             break;
4310         case 'X':
4311             im->unitsexponent = atoi(optarg);
4312             break;
4313         case 'L':
4314             im->unitslength = atoi(optarg);
4315             im->forceleftspace = 1;
4316             break;
4317         case 'T':
4318             im->tabwidth = atof(optarg);
4319             break;
4320         case 'S':
4321             im->step = atoi(optarg);
4322             break;
4323         case 'N':
4324             im->gridfit = 0;
4325             break;
4326         case 'P':
4327             im->with_markup = 1;
4328             break;
4329         case 's':
4330             if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4331                 rrd_set_error("start time: %s", parsetime_error);
4332                 return;
4333             }
4334             break;
4335         case 'e':
4336             if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4337                 rrd_set_error("end time: %s", parsetime_error);
4338                 return;
4339             }
4340             break;
4341         case 'x':
4342             if (strcmp(optarg, "none") == 0) {
4343                 im->draw_x_grid = 0;
4344                 break;
4345             };
4346             if (sscanf(optarg,
4347                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4348                        scan_gtm,
4349                        &im->xlab_user.gridst,
4350                        scan_mtm,
4351                        &im->xlab_user.mgridst,
4352                        scan_ltm,
4353                        &im->xlab_user.labst,
4354                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4355                 strncpy(im->xlab_form, optarg + stroff,
4356                         sizeof(im->xlab_form) - 1);
4357                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4358                 if ((int)
4359                     (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4360                     rrd_set_error("unknown keyword %s", scan_gtm);
4361                     return;
4362                 } else if ((int)
4363                            (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4364                            == -1) {
4365                     rrd_set_error("unknown keyword %s", scan_mtm);
4366                     return;
4367                 } else if ((int)
4368                            (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4369                     rrd_set_error("unknown keyword %s", scan_ltm);
4370                     return;
4371                 }
4372                 im->xlab_user.minsec = 1;
4373                 im->xlab_user.stst = im->xlab_form;
4374             } else {
4375                 rrd_set_error("invalid x-grid format");
4376                 return;
4377             }
4378             break;
4379         case 'y':
4381             if (strcmp(optarg, "none") == 0) {
4382                 im->draw_y_grid = 0;
4383                 break;
4384             };
4385             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4386                 if (im->ygridstep <= 0) {
4387                     rrd_set_error("grid step must be > 0");
4388                     return;
4389                 } else if (im->ylabfact < 1) {
4390                     rrd_set_error("label factor must be > 0");
4391                     return;
4392                 }
4393             } else {
4394                 rrd_set_error("invalid y-grid format");
4395                 return;
4396             }
4397             break;
4398         case 1007:
4399             im->draw_3d_border = atoi(optarg);
4400             break;
4401         case 1008: /* grid-dash */
4402             if(sscanf(optarg,
4403                       "%lf:%lf",
4404                       &im->grid_dash_on,
4405                       &im->grid_dash_off) != 2) {
4406                 rrd_set_error("expected grid-dash format float:float");
4407                 return;
4408             }
4409             break;   
4410         case 1009: /* enable dynamic labels */
4411             im->dynamic_labels = 1;
4412             break;         
4413         case 1002: /* right y axis */
4415             if(sscanf(optarg,
4416                       "%lf:%lf",
4417                       &im->second_axis_scale,
4418                       &im->second_axis_shift) == 2) {
4419                 if(im->second_axis_scale==0){
4420                     rrd_set_error("the second_axis_scale  must not be 0");
4421                     return;
4422                 }
4423             } else {
4424                 rrd_set_error("invalid right-axis format expected scale:shift");
4425                 return;
4426             }
4427             break;
4428         case 1003:
4429             strncpy(im->second_axis_legend,optarg,150);
4430             im->second_axis_legend[150]='\0';
4431             break;
4432         case 1004:
4433             if (bad_format(optarg)){
4434                 rrd_set_error("use either %le or %lf formats");
4435                 return;
4436             }
4437             strncpy(im->second_axis_format,optarg,150);
4438             im->second_axis_format[150]='\0';
4439             break;
4440         case 'v':
4441             strncpy(im->ylegend, optarg, 150);
4442             im->ylegend[150] = '\0';
4443             break;
4444         case 'u':
4445             im->maxval = atof(optarg);
4446             break;
4447         case 'l':
4448             im->minval = atof(optarg);
4449             break;
4450         case 'b':
4451             im->base = atol(optarg);
4452             if (im->base != 1024 && im->base != 1000) {
4453                 rrd_set_error
4454                     ("the only sensible value for base apart from 1000 is 1024");
4455                 return;
4456             }
4457             break;
4458         case 'w':
4459             long_tmp = atol(optarg);
4460             if (long_tmp < 10) {
4461                 rrd_set_error("width below 10 pixels");
4462                 return;
4463             }
4464             im->xsize = long_tmp;
4465             break;
4466         case 'h':
4467             long_tmp = atol(optarg);
4468             if (long_tmp < 10) {
4469                 rrd_set_error("height below 10 pixels");
4470                 return;
4471             }
4472             im->ysize = long_tmp;
4473             break;
4474         case 'D':
4475             im->extra_flags |= FULL_SIZE_MODE;
4476             break;
4477         case 'i':
4478             /* interlaced png not supported at the moment */
4479             break;
4480         case 'r':
4481             im->rigid = 1;
4482             break;
4483         case 'f':
4484             im->imginfo = optarg;
4485             break;
4486         case 'a':
4487             if ((int)
4488                 (im->imgformat = if_conv(optarg)) == -1) {
4489                 rrd_set_error("unsupported graphics format '%s'", optarg);
4490                 return;
4491             }
4492             break;
4493         case 'z':
4494             im->lazy = 1;
4495             break;
4496         case 'E':
4497             im->slopemode = 1;
4498             break;
4499         case 'o':
4500             im->logarithmic = 1;
4501             break;
4502         case 'c':
4503             if (sscanf(optarg,
4504                        "%10[A-Z]#%n%8lx%n",
4505                        col_nam, &col_start, &color, &col_end) == 2) {
4506                 int       ci;
4507                 int       col_len = col_end - col_start;
4509                 switch (col_len) {
4510                 case 3:
4511                     color =
4512                         (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4513                                                          0x011000) |
4514                          ((color & 0x00F)
4515                           * 0x001100)
4516                          | 0x000000FF);
4517                     break;
4518                 case 4:
4519                     color =
4520                         (((color & 0xF000) *
4521                           0x11000) | ((color & 0x0F00) *
4522                                       0x01100) | ((color &
4523                                                    0x00F0) *
4524                                                   0x00110) |
4525                          ((color & 0x000F) * 0x00011)
4526                         );
4527                     break;
4528                 case 6:
4529                     color = (color << 8) + 0xff /* shift left by 8 */ ;
4530                     break;
4531                 case 8:
4532                     break;
4533                 default:
4534                     rrd_set_error("the color format is #RRGGBB[AA]");
4535                     return;
4536                 }
4537                 if ((ci = grc_conv(col_nam)) != -1) {
4538                     im->graph_col[ci] = gfx_hex_to_col(color);
4539                 } else {
4540                     rrd_set_error("invalid color name '%s'", col_nam);
4541                     return;
4542                 }
4543             } else {
4544                 rrd_set_error("invalid color def format");
4545                 return;
4546             }
4547             break;
4548         case 'n':{
4549             char      prop[15];
4550             double    size = 1;
4551             int       end;
4553             if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4554                 int       sindex, propidx;
4556                 if ((sindex = text_prop_conv(prop)) != -1) {
4557                     for (propidx = sindex;
4558                          propidx < TEXT_PROP_LAST; propidx++) {
4559                         if (size > 0) {
4560                             rrd_set_font_desc(im,propidx,NULL,size);
4561                         }
4562                         if ((int) strlen(optarg) > end+2) {
4563                             if (optarg[end] == ':') {
4564                                 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4565                             } else {
4566                                 rrd_set_error
4567                                     ("expected : after font size in '%s'",
4568                                      optarg);
4569                                 return;
4570                             }
4571                         }
4572                         /* only run the for loop for DEFAULT (0) for
4573                            all others, we break here. woodo programming */
4574                         if (propidx == sindex && sindex != 0)
4575                             break;
4576                     }
4577                 } else {
4578                     rrd_set_error("invalid fonttag '%s'", prop);
4579                     return;
4580                 }
4581             } else {
4582                 rrd_set_error("invalid text property format");
4583                 return;
4584             }
4585             break;
4586         }
4587         case 'm':
4588             im->zoom = atof(optarg);
4589             if (im->zoom <= 0.0) {
4590                 rrd_set_error("zoom factor must be > 0");
4591                 return;
4592             }
4593             break;
4594         case 't':
4595             strncpy(im->title, optarg, 150);
4596             im->title[150] = '\0';
4597             break;
4598         case 'R':
4599             if (strcmp(optarg, "normal") == 0) {
4600                 cairo_font_options_set_antialias
4601                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4602                 cairo_font_options_set_hint_style
4603                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4604             } else if (strcmp(optarg, "light") == 0) {
4605                 cairo_font_options_set_antialias
4606                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4607                 cairo_font_options_set_hint_style
4608                     (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4609             } else if (strcmp(optarg, "mono") == 0) {
4610                 cairo_font_options_set_antialias
4611                     (im->font_options, CAIRO_ANTIALIAS_NONE);
4612                 cairo_font_options_set_hint_style
4613                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4614             } else {
4615                 rrd_set_error("unknown font-render-mode '%s'", optarg);
4616                 return;
4617             }
4618             break;
4619         case 'G':
4620             if (strcmp(optarg, "normal") == 0)
4621                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4622             else if (strcmp(optarg, "mono") == 0)
4623                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4624             else {
4625                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4626                 return;
4627             }
4628             break;
4629         case 'B':
4630             /* not supported curently */
4631             break;
4632         case 'W':
4633             strncpy(im->watermark, optarg, 100);
4634             im->watermark[99] = '\0';
4635             break;
4636         case 'd':
4637         {
4638             if (im->daemon_addr != NULL)
4639             {
4640                 rrd_set_error ("You cannot specify --daemon "
4641                         "more than once.");
4642                 return;
4643             }
4645             im->daemon_addr = strdup(optarg);
4646             if (im->daemon_addr == NULL)
4647             {
4648               rrd_set_error("strdup failed");
4649               return;
4650             }
4652             break;
4653         }
4654         case '?':
4655             if (optopt != 0)
4656                 rrd_set_error("unknown option '%c'", optopt);
4657             else
4658                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4659             return;
4660         }
4661     } /* while (1) */
4663     {   /* try to connect to rrdcached */
4664         int status = rrdc_connect(im->daemon_addr);
4665         if (status != 0) return;
4666     }
4668     pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4669     pango_layout_context_changed(im->layout);
4673     if (im->logarithmic && im->minval <= 0) {
4674         rrd_set_error
4675             ("for a logarithmic yaxis you must specify a lower-limit > 0");
4676         return;
4677     }
4679     if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4680         /* error string is set in rrd_parsetime.c */
4681         return;
4682     }
4684     if (start_tmp < 3600 * 24 * 365 * 10) {
4685         rrd_set_error
4686             ("the first entry to fetch should be after 1980 (%ld)",
4687              start_tmp);
4688         return;
4689     }
4691     if (end_tmp < start_tmp) {
4692         rrd_set_error
4693             ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4694         return;
4695     }
4697     im->start = start_tmp;
4698     im->end = end_tmp;
4699     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4702 int rrd_graph_color(
4703     image_desc_t
4704     *im,
4705     char *var,
4706     char *err,
4707     int optional)
4709     char     *color;
4710     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4712     color = strstr(var, "#");
4713     if (color == NULL) {
4714         if (optional == 0) {
4715             rrd_set_error("Found no color in %s", err);
4716             return 0;
4717         }
4718         return 0;
4719     } else {
4720         int       n = 0;
4721         char     *rest;
4722         long unsigned int col;
4724         rest = strstr(color, ":");
4725         if (rest != NULL)
4726             n = rest - color;
4727         else
4728             n = strlen(color);
4729         switch (n) {
4730         case 7:
4731             sscanf(color, "#%6lx%n", &col, &n);
4732             col = (col << 8) + 0xff /* shift left by 8 */ ;
4733             if (n != 7)
4734                 rrd_set_error("Color problem in %s", err);
4735             break;
4736         case 9:
4737             sscanf(color, "#%8lx%n", &col, &n);
4738             if (n == 9)
4739                 break;
4740         default:
4741             rrd_set_error("Color problem in %s", err);
4742         }
4743         if (rrd_test_error())
4744             return 0;
4745         gdp->col = gfx_hex_to_col(col);
4746         return n;
4747     }
4751 int bad_format(
4752     char *fmt)
4754     char     *ptr;
4755     int       n = 0;
4757     ptr = fmt;
4758     while (*ptr != '\0')
4759         if (*ptr++ == '%') {
4761             /* line cannot end with percent char */
4762             if (*ptr == '\0')
4763                 return 1;
4764             /* '%s', '%S' and '%%' are allowed */
4765             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4766                 ptr++;
4767             /* %c is allowed (but use only with vdef!) */
4768             else if (*ptr == 'c') {
4769                 ptr++;
4770                 n = 1;
4771             }
4773             /* or else '% 6.2lf' and such are allowed */
4774             else {
4775                 /* optional padding character */
4776                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4777                     ptr++;
4778                 /* This should take care of 'm.n' with all three optional */
4779                 while (*ptr >= '0' && *ptr <= '9')
4780                     ptr++;
4781                 if (*ptr == '.')
4782                     ptr++;
4783                 while (*ptr >= '0' && *ptr <= '9')
4784                     ptr++;
4785                 /* Either 'le', 'lf' or 'lg' must follow here */
4786                 if (*ptr++ != 'l')
4787                     return 1;
4788                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4789                     ptr++;
4790                 else
4791                     return 1;
4792                 n++;
4793             }
4794         }
4796     return (n != 1);
4800 int vdef_parse(
4801     struct graph_desc_t
4802     *gdes,
4803     const char *const str)
4805     /* A VDEF currently is either "func" or "param,func"
4806      * so the parsing is rather simple.  Change if needed.
4807      */
4808     double    param;
4809     char      func[30];
4810     int       n;
4812     n = 0;
4813     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4814     if (n == (int) strlen(str)) {   /* matched */
4815         ;
4816     } else {
4817         n = 0;
4818         sscanf(str, "%29[A-Z]%n", func, &n);
4819         if (n == (int) strlen(str)) {   /* matched */
4820             param = DNAN;
4821         } else {
4822             rrd_set_error
4823                 ("Unknown function string '%s' in VDEF '%s'",
4824                  str, gdes->vname);
4825             return -1;
4826         }
4827     }
4828     if (!strcmp("PERCENT", func))
4829         gdes->vf.op = VDEF_PERCENT;
4830     else if (!strcmp("PERCENTNAN", func))
4831         gdes->vf.op = VDEF_PERCENTNAN;
4832     else if (!strcmp("MAXIMUM", func))
4833         gdes->vf.op = VDEF_MAXIMUM;
4834     else if (!strcmp("AVERAGE", func))
4835         gdes->vf.op = VDEF_AVERAGE;
4836     else if (!strcmp("STDEV", func))
4837         gdes->vf.op = VDEF_STDEV;
4838     else if (!strcmp("MINIMUM", func))
4839         gdes->vf.op = VDEF_MINIMUM;
4840     else if (!strcmp("TOTAL", func))
4841         gdes->vf.op = VDEF_TOTAL;
4842     else if (!strcmp("FIRST", func))
4843         gdes->vf.op = VDEF_FIRST;
4844     else if (!strcmp("LAST", func))
4845         gdes->vf.op = VDEF_LAST;
4846     else if (!strcmp("LSLSLOPE", func))
4847         gdes->vf.op = VDEF_LSLSLOPE;
4848     else if (!strcmp("LSLINT", func))
4849         gdes->vf.op = VDEF_LSLINT;
4850     else if (!strcmp("LSLCORREL", func))
4851         gdes->vf.op = VDEF_LSLCORREL;
4852     else {
4853         rrd_set_error
4854             ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4855         return -1;
4856     };
4857     switch (gdes->vf.op) {
4858     case VDEF_PERCENT:
4859     case VDEF_PERCENTNAN:
4860         if (isnan(param)) { /* no parameter given */
4861             rrd_set_error
4862                 ("Function '%s' needs parameter in VDEF '%s'\n",
4863                  func, gdes->vname);
4864             return -1;
4865         };
4866         if (param >= 0.0 && param <= 100.0) {
4867             gdes->vf.param = param;
4868             gdes->vf.val = DNAN;    /* undefined */
4869             gdes->vf.when = 0;  /* undefined */
4870         } else {
4871             rrd_set_error
4872                 ("Parameter '%f' out of range in VDEF '%s'\n",
4873                  param, gdes->vname);
4874             return -1;
4875         };
4876         break;
4877     case VDEF_MAXIMUM:
4878     case VDEF_AVERAGE:
4879     case VDEF_STDEV:
4880     case VDEF_MINIMUM:
4881     case VDEF_TOTAL:
4882     case VDEF_FIRST:
4883     case VDEF_LAST:
4884     case VDEF_LSLSLOPE:
4885     case VDEF_LSLINT:
4886     case VDEF_LSLCORREL:
4887         if (isnan(param)) {
4888             gdes->vf.param = DNAN;
4889             gdes->vf.val = DNAN;
4890             gdes->vf.when = 0;
4891         } else {
4892             rrd_set_error
4893                 ("Function '%s' needs no parameter in VDEF '%s'\n",
4894                  func, gdes->vname);
4895             return -1;
4896         };
4897         break;
4898     };
4899     return 0;
4903 int vdef_calc(
4904     image_desc_t *im,
4905     int gdi)
4907     graph_desc_t *src, *dst;
4908     rrd_value_t *data;
4909     long      step, steps;
4911     dst = &im->gdes[gdi];
4912     src = &im->gdes[dst->vidx];
4913     data = src->data + src->ds;
4915     steps = (src->end - src->start) / src->step;
4916 #if 0
4917     printf
4918         ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4919          src->start, src->end, steps);
4920 #endif
4921     switch (dst->vf.op) {
4922     case VDEF_PERCENT:{
4923         rrd_value_t *array;
4924         int       field;
4925         if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4926             rrd_set_error("malloc VDEV_PERCENT");
4927             return -1;
4928         }
4929         for (step = 0; step < steps; step++) {
4930             array[step] = data[step * src->ds_cnt];
4931         }
4932         qsort(array, step, sizeof(double), vdef_percent_compar);
4933         field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
4934         dst->vf.val = array[field];
4935         dst->vf.when = 0;   /* no time component */
4936         free(array);
4937 #if 0
4938         for (step = 0; step < steps; step++)
4939             printf("DEBUG: %3li:%10.2f %c\n",
4940                    step, array[step], step == field ? '*' : ' ');
4941 #endif
4942     }
4943         break;
4944     case VDEF_PERCENTNAN:{
4945         rrd_value_t *array;
4946         int       field;
4947        /* count number of "valid" values */
4948        int nancount=0;
4949        for (step = 0; step < steps; step++) {
4950          if (!isnan(data[step * src->ds_cnt])) { nancount++; }
4951        }
4952        /* and allocate it */
4953         if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
4954             rrd_set_error("malloc VDEV_PERCENT");
4955             return -1;
4956         }
4957        /* and fill it in */
4958        field=0;
4959         for (step = 0; step < steps; step++) {
4960            if (!isnan(data[step * src->ds_cnt])) {
4961                 array[field] = data[step * src->ds_cnt];
4962                field++;
4963             }
4964         }
4965         qsort(array, nancount, sizeof(double), vdef_percent_compar);
4966         field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
4967         dst->vf.val = array[field];
4968         dst->vf.when = 0;   /* no time component */
4969         free(array);
4970     }
4971         break;
4972     case VDEF_MAXIMUM:
4973         step = 0;
4974         while (step != steps && isnan(data[step * src->ds_cnt]))
4975             step++;
4976         if (step == steps) {
4977             dst->vf.val = DNAN;
4978             dst->vf.when = 0;
4979         } else {
4980             dst->vf.val = data[step * src->ds_cnt];
4981             dst->vf.when = src->start + (step + 1) * src->step;
4982         }
4983         while (step != steps) {
4984             if (finite(data[step * src->ds_cnt])) {
4985                 if (data[step * src->ds_cnt] > dst->vf.val) {
4986                     dst->vf.val = data[step * src->ds_cnt];
4987                     dst->vf.when = src->start + (step + 1) * src->step;
4988                 }
4989             }
4990             step++;
4991         }
4992         break;
4993     case VDEF_TOTAL:
4994     case VDEF_STDEV:
4995     case VDEF_AVERAGE:{
4996         int       cnt = 0;
4997         double    sum = 0.0;
4998         double    average = 0.0;
5000         for (step = 0; step < steps; step++) {
5001             if (finite(data[step * src->ds_cnt])) {
5002                 sum += data[step * src->ds_cnt];
5003                 cnt++;
5004             };
5005         }
5006         if (cnt) {
5007             if (dst->vf.op == VDEF_TOTAL) {
5008                 dst->vf.val = sum * src->step;
5009                 dst->vf.when = 0;   /* no time component */
5010             } else if (dst->vf.op == VDEF_AVERAGE) {
5011                 dst->vf.val = sum / cnt;
5012                 dst->vf.when = 0;   /* no time component */
5013             } else {
5014                 average = sum / cnt;
5015                 sum = 0.0;
5016                 for (step = 0; step < steps; step++) {
5017                     if (finite(data[step * src->ds_cnt])) {
5018                         sum += pow((data[step * src->ds_cnt] - average), 2.0);
5019                     };
5020                 }
5021                 dst->vf.val = pow(sum / cnt, 0.5);
5022                 dst->vf.when = 0;   /* no time component */
5023             };
5024         } else {
5025             dst->vf.val = DNAN;
5026             dst->vf.when = 0;
5027         }
5028     }
5029         break;
5030     case VDEF_MINIMUM:
5031         step = 0;
5032         while (step != steps && isnan(data[step * src->ds_cnt]))
5033             step++;
5034         if (step == steps) {
5035             dst->vf.val = DNAN;
5036             dst->vf.when = 0;
5037         } else {
5038             dst->vf.val = data[step * src->ds_cnt];
5039             dst->vf.when = src->start + (step + 1) * src->step;
5040         }
5041         while (step != steps) {
5042             if (finite(data[step * src->ds_cnt])) {
5043                 if (data[step * src->ds_cnt] < dst->vf.val) {
5044                     dst->vf.val = data[step * src->ds_cnt];
5045                     dst->vf.when = src->start + (step + 1) * src->step;
5046                 }
5047             }
5048             step++;
5049         }
5050         break;
5051     case VDEF_FIRST:
5052         /* The time value returned here is one step before the
5053          * actual time value.  This is the start of the first
5054          * non-NaN interval.
5055          */
5056         step = 0;
5057         while (step != steps && isnan(data[step * src->ds_cnt]))
5058             step++;
5059         if (step == steps) {    /* all entries were NaN */
5060             dst->vf.val = DNAN;
5061             dst->vf.when = 0;
5062         } else {
5063             dst->vf.val = data[step * src->ds_cnt];
5064             dst->vf.when = src->start + step * src->step;
5065         }
5066         break;
5067     case VDEF_LAST:
5068         /* The time value returned here is the
5069          * actual time value.  This is the end of the last
5070          * non-NaN interval.
5071          */
5072         step = steps - 1;
5073         while (step >= 0 && isnan(data[step * src->ds_cnt]))
5074             step--;
5075         if (step < 0) { /* all entries were NaN */
5076             dst->vf.val = DNAN;
5077             dst->vf.when = 0;
5078         } else {
5079             dst->vf.val = data[step * src->ds_cnt];
5080             dst->vf.when = src->start + (step + 1) * src->step;
5081         }
5082         break;
5083     case VDEF_LSLSLOPE:
5084     case VDEF_LSLINT:
5085     case VDEF_LSLCORREL:{
5086         /* Bestfit line by linear least squares method */
5088         int       cnt = 0;
5089         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5091         SUMx = 0;
5092         SUMy = 0;
5093         SUMxy = 0;
5094         SUMxx = 0;
5095         SUMyy = 0;
5096         for (step = 0; step < steps; step++) {
5097             if (finite(data[step * src->ds_cnt])) {
5098                 cnt++;
5099                 SUMx += step;
5100                 SUMxx += step * step;
5101                 SUMxy += step * data[step * src->ds_cnt];
5102                 SUMy += data[step * src->ds_cnt];
5103                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5104             };
5105         }
5107         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5108         y_intercept = (SUMy - slope * SUMx) / cnt;
5109         correl =
5110             (SUMxy -
5111              (SUMx * SUMy) / cnt) /
5112             sqrt((SUMxx -
5113                   (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5114         if (cnt) {
5115             if (dst->vf.op == VDEF_LSLSLOPE) {
5116                 dst->vf.val = slope;
5117                 dst->vf.when = 0;
5118             } else if (dst->vf.op == VDEF_LSLINT) {
5119                 dst->vf.val = y_intercept;
5120                 dst->vf.when = 0;
5121             } else if (dst->vf.op == VDEF_LSLCORREL) {
5122                 dst->vf.val = correl;
5123                 dst->vf.when = 0;
5124             };
5125         } else {
5126             dst->vf.val = DNAN;
5127             dst->vf.when = 0;
5128         }
5129     }
5130         break;
5131     }
5132     return 0;
5135 /* NaN < -INF < finite_values < INF */
5136 int vdef_percent_compar(
5137     const void
5138     *a,
5139     const void
5140     *b)
5142     /* Equality is not returned; this doesn't hurt except
5143      * (maybe) for a little performance.
5144      */
5146     /* First catch NaN values. They are smallest */
5147     if (isnan(*(double *) a))
5148         return -1;
5149     if (isnan(*(double *) b))
5150         return 1;
5151     /* NaN doesn't reach this part so INF and -INF are extremes.
5152      * The sign from isinf() is compatible with the sign we return
5153      */
5154     if (isinf(*(double *) a))
5155         return isinf(*(double *) a);
5156     if (isinf(*(double *) b))
5157         return isinf(*(double *) b);
5158     /* If we reach this, both values must be finite */
5159     if (*(double *) a < *(double *) b)
5160         return -1;
5161     else
5162         return 1;
5165 void grinfo_push(
5166     image_desc_t *im,
5167     char *key,
5168     rrd_info_type_t type,
5169     rrd_infoval_t value)
5171     im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5172     if (im->grinfo == NULL) {
5173         im->grinfo = im->grinfo_current;
5174     }