Code

It seems that function graph_size_location() doesn't call xtr(im, 0) if (im->extra_fl...
[rrdtool-all.git] / program / src / rrd_graph.c
1 /****************************************************************************
2  * RRDtool 1.4.5  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             /* must reduce to at least im->step
900                otherwhise we end up with more data than we can handle in the 
901                chart and visibility of data will be random */            
902             im->gdes[i].step = max(im->gdes[i].step,im->step);
903             if (ft_step < im->gdes[i].step) {
904                 reduce_data(im->gdes[i].cf_reduce,
905                             ft_step,
906                             &im->gdes[i].start,
907                             &im->gdes[i].end,
908                             &im->gdes[i].step,
909                             &im->gdes[i].ds_cnt, &im->gdes[i].data);
910             } else {
911                 im->gdes[i].step = ft_step;
912             }
913         }
915         /* lets see if the required data source is really there */
916         for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
917             if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
918                 im->gdes[i].ds = ii;
919             }
920         }
921         if (im->gdes[i].ds == -1) {
922             rrd_set_error("No DS called '%s' in '%s'",
923                           im->gdes[i].ds_nam, im->gdes[i].rrd);
924             return -1;
925         }
927     }
928     return 0;
931 /* evaluate the expressions in the CDEF functions */
933 /*************************************************************
934  * CDEF stuff
935  *************************************************************/
937 long find_var_wrapper(
938     void *arg1,
939     char *key)
941     return find_var((image_desc_t *) arg1, key);
944 /* find gdes containing var*/
945 long find_var(
946     image_desc_t *im,
947     char *key)
949     long      ii;
951     for (ii = 0; ii < im->gdes_c - 1; ii++) {
952         if ((im->gdes[ii].gf == GF_DEF
953              || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
954             && (strcmp(im->gdes[ii].vname, key) == 0)) {
955             return ii;
956         }
957     }
958     return -1;
961 /* find the greatest common divisor for all the numbers
962    in the 0 terminated num array */
963 long lcd(
964     long *num)
966     long      rest;
967     int       i;
969     for (i = 0; num[i + 1] != 0; i++) {
970         do {
971             rest = num[i] % num[i + 1];
972             num[i] = num[i + 1];
973             num[i + 1] = rest;
974         } while (rest != 0);
975         num[i + 1] = num[i];
976     }
977 /*    return i==0?num[i]:num[i-1]; */
978     return num[i];
981 /* run the rpn calculator on all the VDEF and CDEF arguments */
982 int data_calc(
983     image_desc_t *im)
986     int       gdi;
987     int       dataidx;
988     long     *steparray, rpi;
989     int       stepcnt;
990     time_t    now;
991     rpnstack_t rpnstack;
993     rpnstack_init(&rpnstack);
995     for (gdi = 0; gdi < im->gdes_c; gdi++) {
996         /* Look for GF_VDEF and GF_CDEF in the same loop,
997          * so CDEFs can use VDEFs and vice versa
998          */
999         switch (im->gdes[gdi].gf) {
1000         case GF_XPORT:
1001             break;
1002         case GF_SHIFT:{
1003             graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
1005             /* remove current shift */
1006             vdp->start -= vdp->shift;
1007             vdp->end -= vdp->shift;
1009             /* vdef */
1010             if (im->gdes[gdi].shidx >= 0)
1011                 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
1012             /* constant */
1013             else
1014                 vdp->shift = im->gdes[gdi].shval;
1016             /* normalize shift to multiple of consolidated step */
1017             vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
1019             /* apply shift */
1020             vdp->start += vdp->shift;
1021             vdp->end += vdp->shift;
1022             break;
1023         }
1024         case GF_VDEF:
1025             /* A VDEF has no DS.  This also signals other parts
1026              * of rrdtool that this is a VDEF value, not a CDEF.
1027              */
1028             im->gdes[gdi].ds_cnt = 0;
1029             if (vdef_calc(im, gdi)) {
1030                 rrd_set_error("Error processing VDEF '%s'",
1031                               im->gdes[gdi].vname);
1032                 rpnstack_free(&rpnstack);
1033                 return -1;
1034             }
1035             break;
1036         case GF_CDEF:
1037             im->gdes[gdi].ds_cnt = 1;
1038             im->gdes[gdi].ds = 0;
1039             im->gdes[gdi].data_first = 1;
1040             im->gdes[gdi].start = 0;
1041             im->gdes[gdi].end = 0;
1042             steparray = NULL;
1043             stepcnt = 0;
1044             dataidx = -1;
1046             /* Find the variables in the expression.
1047              * - VDEF variables are substituted by their values
1048              *   and the opcode is changed into OP_NUMBER.
1049              * - CDEF variables are analized for their step size,
1050              *   the lowest common denominator of all the step
1051              *   sizes of the data sources involved is calculated
1052              *   and the resulting number is the step size for the
1053              *   resulting data source.
1054              */
1055             for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1056                 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1057                     im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1058                     long      ptr = im->gdes[gdi].rpnp[rpi].ptr;
1060                     if (im->gdes[ptr].ds_cnt == 0) {    /* this is a VDEF data source */
1061 #if 0
1062                         printf
1063                             ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1064                              im->gdes[gdi].vname, im->gdes[ptr].vname);
1065                         printf("DEBUG: value from vdef is %f\n",
1066                                im->gdes[ptr].vf.val);
1067 #endif
1068                         im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1069                         im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1070                     } else {    /* normal variables and PREF(variables) */
1072                         /* add one entry to the array that keeps track of the step sizes of the
1073                          * data sources going into the CDEF. */
1074                         if ((steparray =
1075                              (long*)rrd_realloc(steparray,
1076                                          (++stepcnt +
1077                                           1) * sizeof(*steparray))) == NULL) {
1078                             rrd_set_error("realloc steparray");
1079                             rpnstack_free(&rpnstack);
1080                             return -1;
1081                         };
1083                         steparray[stepcnt - 1] = im->gdes[ptr].step;
1085                         /* adjust start and end of cdef (gdi) so
1086                          * that it runs from the latest start point
1087                          * to the earliest endpoint of any of the
1088                          * rras involved (ptr)
1089                          */
1091                         if (im->gdes[gdi].start < im->gdes[ptr].start)
1092                             im->gdes[gdi].start = im->gdes[ptr].start;
1094                         if (im->gdes[gdi].end == 0 ||
1095                             im->gdes[gdi].end > im->gdes[ptr].end)
1096                             im->gdes[gdi].end = im->gdes[ptr].end;
1098                         /* store pointer to the first element of
1099                          * the rra providing data for variable,
1100                          * further save step size and data source
1101                          * count of this rra
1102                          */
1103                         im->gdes[gdi].rpnp[rpi].data =
1104                             im->gdes[ptr].data + im->gdes[ptr].ds;
1105                         im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1106                         im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1108                         /* backoff the *.data ptr; this is done so
1109                          * rpncalc() function doesn't have to treat
1110                          * the first case differently
1111                          */
1112                     }   /* if ds_cnt != 0 */
1113                 }       /* if OP_VARIABLE */
1114             }           /* loop through all rpi */
1116             /* move the data pointers to the correct period */
1117             for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1118                 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1119                     im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1120                     long      ptr = im->gdes[gdi].rpnp[rpi].ptr;
1121                     long      diff =
1122                         im->gdes[gdi].start - im->gdes[ptr].start;
1124                     if (diff > 0)
1125                         im->gdes[gdi].rpnp[rpi].data +=
1126                             (diff / im->gdes[ptr].step) *
1127                             im->gdes[ptr].ds_cnt;
1128                 }
1129             }
1131             if (steparray == NULL) {
1132                 rrd_set_error("rpn expressions without DEF"
1133                               " or CDEF variables are not supported");
1134                 rpnstack_free(&rpnstack);
1135                 return -1;
1136             }
1137             steparray[stepcnt] = 0;
1138             /* Now find the resulting step.  All steps in all
1139              * used RRAs have to be visited
1140              */
1141             im->gdes[gdi].step = lcd(steparray);
1142             free(steparray);
1143             if ((im->gdes[gdi].data = (rrd_value_t*)malloc(((im->gdes[gdi].end -
1144                                                im->gdes[gdi].start)
1145                                               / im->gdes[gdi].step)
1146                                              * sizeof(double))) == NULL) {
1147                 rrd_set_error("malloc im->gdes[gdi].data");
1148                 rpnstack_free(&rpnstack);
1149                 return -1;
1150             }
1152             /* Step through the new cdef results array and
1153              * calculate the values
1154              */
1155             for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1156                  now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1157                 rpnp_t   *rpnp = im->gdes[gdi].rpnp;
1159                 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1160                  * in this case we are advancing by timesteps;
1161                  * we use the fact that time_t is a synonym for long
1162                  */
1163                 if (rpn_calc(rpnp, &rpnstack, (long) now,
1164                              im->gdes[gdi].data, ++dataidx) == -1) {
1165                     /* rpn_calc sets the error string */
1166                     rpnstack_free(&rpnstack);
1167                     return -1;
1168                 }
1169             }           /* enumerate over time steps within a CDEF */
1170             break;
1171         default:
1172             continue;
1173         }
1174     }                   /* enumerate over CDEFs */
1175     rpnstack_free(&rpnstack);
1176     return 0;
1179 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1180 /* yes we are loosing precision by doing tos with floats instead of doubles
1181    but it seems more stable this way. */
1183 static int AlmostEqual2sComplement(
1184     float A,
1185     float B,
1186     int maxUlps)
1189     int       aInt = *(int *) &A;
1190     int       bInt = *(int *) &B;
1191     int       intDiff;
1193     /* Make sure maxUlps is non-negative and small enough that the
1194        default NAN won't compare as equal to anything.  */
1196     /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1198     /* Make aInt lexicographically ordered as a twos-complement int */
1200     if (aInt < 0)
1201         aInt = 0x80000000l - aInt;
1203     /* Make bInt lexicographically ordered as a twos-complement int */
1205     if (bInt < 0)
1206         bInt = 0x80000000l - bInt;
1208     intDiff = abs(aInt - bInt);
1210     if (intDiff <= maxUlps)
1211         return 1;
1213     return 0;
1216 /* massage data so, that we get one value for each x coordinate in the graph */
1217 int data_proc(
1218     image_desc_t *im)
1220     long      i, ii;
1221     double    pixstep = (double) (im->end - im->start)
1222         / (double) im->xsize;   /* how much time
1223                                    passes in one pixel */
1224     double    paintval;
1225     double    minval = DNAN, maxval = DNAN;
1227     unsigned long gr_time;
1229     /* memory for the processed data */
1230     for (i = 0; i < im->gdes_c; i++) {
1231         if ((im->gdes[i].gf == GF_LINE) ||
1232             (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1233             if ((im->gdes[i].p_data = (rrd_value_t*)malloc((im->xsize + 1)
1234                                              * sizeof(rrd_value_t))) == NULL) {
1235                 rrd_set_error("malloc data_proc");
1236                 return -1;
1237             }
1238         }
1239     }
1241     for (i = 0; i < im->xsize; i++) {   /* for each pixel */
1242         long      vidx;
1244         gr_time = im->start + pixstep * i;  /* time of the current step */
1245         paintval = 0.0;
1247         for (ii = 0; ii < im->gdes_c; ii++) {
1248             double    value;
1250             switch (im->gdes[ii].gf) {
1251             case GF_LINE:
1252             case GF_AREA:
1253             case GF_TICK:
1254                 if (!im->gdes[ii].stack)
1255                     paintval = 0.0;
1256                 value = im->gdes[ii].yrule;
1257                 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1258                     /* The time of the data doesn't necessarily match
1259                      ** the time of the graph. Beware.
1260                      */
1261                     vidx = im->gdes[ii].vidx;
1262                     if (im->gdes[vidx].gf == GF_VDEF) {
1263                         value = im->gdes[vidx].vf.val;
1264                     } else
1265                         if (((long int) gr_time >=
1266                              (long int) im->gdes[vidx].start)
1267                             && ((long int) gr_time <
1268                                 (long int) im->gdes[vidx].end)) {
1269                         value = im->gdes[vidx].data[(unsigned long)
1270                                                     floor((double)
1271                                                           (gr_time -
1272                                                            im->gdes[vidx].
1273                                                            start)
1274                                                           /
1275                                                           im->gdes[vidx].step)
1276                                                     * im->gdes[vidx].ds_cnt +
1277                                                     im->gdes[vidx].ds];
1278                     } else {
1279                         value = DNAN;
1280                     }
1281                 };
1283                 if (!isnan(value)) {
1284                     paintval += value;
1285                     im->gdes[ii].p_data[i] = paintval;
1286                     /* GF_TICK: the data values are not
1287                      ** relevant for min and max
1288                      */
1289                     if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1290                         if ((isnan(minval) || paintval < minval) &&
1291                             !(im->logarithmic && paintval <= 0.0))
1292                             minval = paintval;
1293                         if (isnan(maxval) || paintval > maxval)
1294                             maxval = paintval;
1295                     }
1296                 } else {
1297                     im->gdes[ii].p_data[i] = DNAN;
1298                 }
1299                 break;
1300             case GF_STACK:
1301                 rrd_set_error
1302                     ("STACK should already be turned into LINE or AREA here");
1303                 return -1;
1304                 break;
1305             default:
1306                 break;
1307             }
1308         }
1309     }
1311     /* if min or max have not been asigned a value this is because
1312        there was no data in the graph ... this is not good ...
1313        lets set these to dummy values then ... */
1315     if (im->logarithmic) {
1316         if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1317             minval = 0.0;   /* catching this right away below */
1318             maxval = 5.1;
1319         }
1320         /* in logarithm mode, where minval is smaller or equal
1321            to 0 make the beast just way smaller than maxval */
1322         if (minval <= 0) {
1323             minval = maxval / 10e8;
1324         }
1325     } else {
1326         if (isnan(minval) || isnan(maxval)) {
1327             minval = 0.0;
1328             maxval = 1.0;
1329         }
1330     }
1332     /* adjust min and max values given by the user */
1333     /* for logscale we add something on top */
1334     if (isnan(im->minval)
1335         || ((!im->rigid) && im->minval > minval)
1336         ) {
1337         if (im->logarithmic)
1338             im->minval = minval / 2.0;
1339         else
1340             im->minval = minval;
1341     }
1342     if (isnan(im->maxval)
1343         || (!im->rigid && im->maxval < maxval)
1344         ) {
1345         if (im->logarithmic)
1346             im->maxval = maxval * 2.0;
1347         else
1348             im->maxval = maxval;
1349     }
1351     /* make sure min is smaller than max */
1352     if (im->minval > im->maxval) {
1353         if (im->minval > 0)
1354             im->minval = 0.99 * im->maxval;
1355         else
1356             im->minval = 1.01 * im->maxval;
1357     }
1359     /* make sure min and max are not equal */
1360     if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1361         if (im->maxval > 0)
1362             im->maxval *= 1.01;
1363         else
1364             im->maxval *= 0.99;
1366         /* make sure min and max are not both zero */
1367         if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1368             im->maxval = 1.0;
1369         }
1370     }
1371     return 0;
1374 static int find_first_weekday(void){
1375     static int first_weekday = -1;
1376     if (first_weekday == -1){
1377 #ifdef HAVE__NL_TIME_WEEK_1STDAY
1378         /* according to http://sourceware.org/ml/libc-locales/2009-q1/msg00011.html */
1379         long week_1stday_l = (long) nl_langinfo (_NL_TIME_WEEK_1STDAY);
1380         if (week_1stday_l == 19971130) first_weekday = 0; /* Sun */
1381         else if (week_1stday_l == 19971201) first_weekday = 1; /* Mon */
1382         else first_weekday = 1; /* we go for a monday default */
1383 #else
1384         first_weekday = 1;
1385 #endif
1386     }
1387     return first_weekday;
1390 /* identify the point where the first gridline, label ... gets placed */
1392 time_t find_first_time(
1393     time_t start,       /* what is the initial time */
1394     enum tmt_en baseint,    /* what is the basic interval */
1395     long basestep       /* how many if these do we jump a time */
1396     )
1398     struct tm tm;
1400     localtime_r(&start, &tm);
1402     switch (baseint) {
1403     case TMT_SECOND:
1404         tm.       tm_sec -= tm.tm_sec % basestep;
1406         break;
1407     case TMT_MINUTE:
1408         tm.       tm_sec = 0;
1409         tm.       tm_min -= tm.tm_min % basestep;
1411         break;
1412     case TMT_HOUR:
1413         tm.       tm_sec = 0;
1414         tm.       tm_min = 0;
1415         tm.       tm_hour -= tm.tm_hour % basestep;
1417         break;
1418     case TMT_DAY:
1419         /* we do NOT look at the basestep for this ... */
1420         tm.       tm_sec = 0;
1421         tm.       tm_min = 0;
1422         tm.       tm_hour = 0;
1424         break;
1425     case TMT_WEEK:
1426         /* we do NOT look at the basestep for this ... */
1427         tm.       tm_sec = 0;
1428         tm.       tm_min = 0;
1429         tm.       tm_hour = 0;
1430         tm.       tm_mday -= tm.tm_wday - find_first_weekday();
1432         if (tm.tm_wday == 0 && find_first_weekday() > 0)
1433             tm.       tm_mday -= 7; /* we want the *previous* week */
1435         break;
1436     case TMT_MONTH:
1437         tm.       tm_sec = 0;
1438         tm.       tm_min = 0;
1439         tm.       tm_hour = 0;
1440         tm.       tm_mday = 1;
1441         tm.       tm_mon -= tm.tm_mon % basestep;
1443         break;
1445     case TMT_YEAR:
1446         tm.       tm_sec = 0;
1447         tm.       tm_min = 0;
1448         tm.       tm_hour = 0;
1449         tm.       tm_mday = 1;
1450         tm.       tm_mon = 0;
1451         tm.       tm_year -= (
1452     tm.tm_year + 1900) %basestep;
1454     }
1455     return mktime(&tm);
1458 /* identify the point where the next gridline, label ... gets placed */
1459 time_t find_next_time(
1460     time_t current,     /* what is the initial time */
1461     enum tmt_en baseint,    /* what is the basic interval */
1462     long basestep       /* how many if these do we jump a time */
1463     )
1465     struct tm tm;
1466     time_t    madetime;
1468     localtime_r(&current, &tm);
1470     int limit = 2;
1471     switch (baseint) {
1472     case TMT_SECOND: limit = 7200; break;
1473     case TMT_MINUTE: limit = 120; break;
1474     case TMT_HOUR: limit = 2; break;
1475     default: limit = 2; break;
1476     }
1477     do {
1478         switch (baseint) {
1479         case TMT_SECOND:
1480             tm.       tm_sec += basestep;
1482             break;
1483         case TMT_MINUTE:
1484             tm.       tm_min += basestep;
1486             break;
1487         case TMT_HOUR:
1488             tm.       tm_hour += basestep;
1490             break;
1491         case TMT_DAY:
1492             tm.       tm_mday += basestep;
1494             break;
1495         case TMT_WEEK:
1496             tm.       tm_mday += 7 * basestep;
1498             break;
1499         case TMT_MONTH:
1500             tm.       tm_mon += basestep;
1502             break;
1503         case TMT_YEAR:
1504             tm.       tm_year += basestep;
1505         }
1506         madetime = mktime(&tm);
1507     } while (madetime == -1 && limit-- >= 0);   /* this is necessary to skip impossible times
1508                                    like the daylight saving time skips */
1509     return madetime;
1514 /* calculate values required for PRINT and GPRINT functions */
1516 int print_calc(
1517     image_desc_t *im)
1519     long      i, ii, validsteps;
1520     double    printval;
1521     struct tm tmvdef;
1522     int       graphelement = 0;
1523     long      vidx;
1524     int       max_ii;
1525     double    magfact = -1;
1526     char     *si_symb = "";
1527     char     *percent_s;
1528     int       prline_cnt = 0;
1530     /* wow initializing tmvdef is quite a task :-) */
1531     time_t    now = time(NULL);
1533     localtime_r(&now, &tmvdef);
1534     for (i = 0; i < im->gdes_c; i++) {
1535         vidx = im->gdes[i].vidx;
1536         switch (im->gdes[i].gf) {
1537         case GF_PRINT:
1538         case GF_GPRINT:
1539             /* PRINT and GPRINT can now print VDEF generated values.
1540              * There's no need to do any calculations on them as these
1541              * calculations were already made.
1542              */
1543             if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1544                 printval = im->gdes[vidx].vf.val;
1545                 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1546             } else {    /* need to calculate max,min,avg etcetera */
1547                 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1548                           / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1549                 printval = DNAN;
1550                 validsteps = 0;
1551                 for (ii = im->gdes[vidx].ds;
1552                      ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1553                     if (!finite(im->gdes[vidx].data[ii]))
1554                         continue;
1555                     if (isnan(printval)) {
1556                         printval = im->gdes[vidx].data[ii];
1557                         validsteps++;
1558                         continue;
1559                     }
1561                     switch (im->gdes[i].cf) {
1562                     case CF_HWPREDICT:
1563                     case CF_MHWPREDICT:
1564                     case CF_DEVPREDICT:
1565                     case CF_DEVSEASONAL:
1566                     case CF_SEASONAL:
1567                     case CF_AVERAGE:
1568                         validsteps++;
1569                         printval += im->gdes[vidx].data[ii];
1570                         break;
1571                     case CF_MINIMUM:
1572                         printval = min(printval, im->gdes[vidx].data[ii]);
1573                         break;
1574                     case CF_FAILURES:
1575                     case CF_MAXIMUM:
1576                         printval = max(printval, im->gdes[vidx].data[ii]);
1577                         break;
1578                     case CF_LAST:
1579                         printval = im->gdes[vidx].data[ii];
1580                     }
1581                 }
1582                 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1583                     if (validsteps > 1) {
1584                         printval = (printval / validsteps);
1585                     }
1586                 }
1587             }           /* prepare printval */
1589             if (!im->gdes[i].strftm && (percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1590                 /* Magfact is set to -1 upon entry to print_calc.  If it
1591                  * is still less than 0, then we need to run auto_scale.
1592                  * Otherwise, put the value into the correct units.  If
1593                  * the value is 0, then do not set the symbol or magnification
1594                  * so next the calculation will be performed again. */
1595                 if (magfact < 0.0) {
1596                     auto_scale(im, &printval, &si_symb, &magfact);
1597                     if (printval == 0.0)
1598                         magfact = -1.0;
1599                 } else {
1600                     printval /= magfact;
1601                 }
1602                 *(++percent_s) = 's';
1603             } else if (!im->gdes[i].strftm && strstr(im->gdes[i].format, "%s") != NULL) {
1604                 auto_scale(im, &printval, &si_symb, &magfact);
1605             }
1607             if (im->gdes[i].gf == GF_PRINT) {
1608                 rrd_infoval_t prline;
1610                 if (im->gdes[i].strftm) {
1611                     prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1612                     strftime(prline.u_str,
1613                              FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1614                 } else if (bad_format(im->gdes[i].format)) {
1615                     rrd_set_error
1616                         ("bad format for PRINT in '%s'", im->gdes[i].format);
1617                     return -1;
1618                 } else {
1619                     prline.u_str =
1620                         sprintf_alloc(im->gdes[i].format, printval, si_symb);
1621                 }
1622                 grinfo_push(im,
1623                             sprintf_alloc
1624                             ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1625                 free(prline.u_str);
1626             } else {
1627                 /* GF_GPRINT */
1629                 if (im->gdes[i].strftm) {
1630                     strftime(im->gdes[i].legend,
1631                              FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1632                 } else {
1633                     if (bad_format(im->gdes[i].format)) {
1634                         rrd_set_error
1635                             ("bad format for GPRINT in '%s'",
1636                              im->gdes[i].format);
1637                         return -1;
1638                     }
1639 #ifdef HAVE_SNPRINTF
1640                     snprintf(im->gdes[i].legend,
1641                              FMT_LEG_LEN - 2,
1642                              im->gdes[i].format, printval, si_symb);
1643 #else
1644                     sprintf(im->gdes[i].legend,
1645                             im->gdes[i].format, printval, si_symb);
1646 #endif
1647                 }
1648                 graphelement = 1;
1649             }
1650             break;
1651         case GF_LINE:
1652         case GF_AREA:
1653         case GF_TICK:
1654             graphelement = 1;
1655             break;
1656         case GF_HRULE:
1657             if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1658                 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1659             };
1660             graphelement = 1;
1661             break;
1662         case GF_VRULE:
1663             if (im->gdes[i].xrule == 0) {   /* again ... the legend printer needs it */
1664                 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1665             };
1666             graphelement = 1;
1667             break;
1668         case GF_COMMENT:
1669         case GF_TEXTALIGN:
1670         case GF_DEF:
1671         case GF_CDEF:
1672         case GF_VDEF:
1673 #ifdef WITH_PIECHART
1674         case GF_PART:
1675 #endif
1676         case GF_SHIFT:
1677         case GF_XPORT:
1678             break;
1679         case GF_STACK:
1680             rrd_set_error
1681                 ("STACK should already be turned into LINE or AREA here");
1682             return -1;
1683             break;
1684         }
1685     }
1686     return graphelement;
1691 /* place legends with color spots */
1692 int leg_place(
1693     image_desc_t *im,
1694     int calc_width)
1696     /* graph labels */
1697     int       interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1698     int       border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1699     int       fill = 0, fill_last;
1700     double    legendwidth; // = im->ximg - 2 * border;
1701     int       leg_c = 0;
1702     double    leg_x = border;
1703     int       leg_y = 0; //im->yimg;
1704     int       leg_y_prev = 0; // im->yimg;
1705     int       leg_cc;
1706     double    glue = 0;
1707     int       i, ii, mark = 0;
1708     char      default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1709     int      *legspace;
1710     char     *tab;
1711     char      saved_legend[FMT_LEG_LEN + 5];
1713     if(calc_width){
1714         legendwidth = 0;
1715     }
1716     else{
1717         legendwidth = im->legendwidth - 2 * border;
1718     }
1721     if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1722         if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1723             rrd_set_error("malloc for legspace");
1724             return -1;
1725         }
1727         for (i = 0; i < im->gdes_c; i++) {
1728             char      prt_fctn; /*special printfunctions */
1729             if(calc_width){
1730                 strcpy(saved_legend, im->gdes[i].legend);
1731             }
1733             fill_last = fill;
1734             /* hide legends for rules which are not displayed */
1735             if (im->gdes[i].gf == GF_TEXTALIGN) {
1736                 default_txtalign = im->gdes[i].txtalign;
1737             }
1739             if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1740                 if (im->gdes[i].gf == GF_HRULE
1741                     && (im->gdes[i].yrule <
1742                         im->minval || im->gdes[i].yrule > im->maxval))
1743                     im->gdes[i].legend[0] = '\0';
1744                 if (im->gdes[i].gf == GF_VRULE
1745                     && (im->gdes[i].xrule <
1746                         im->start || im->gdes[i].xrule > im->end))
1747                     im->gdes[i].legend[0] = '\0';
1748             }
1750             /* turn \\t into tab */
1751             while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1752                 memmove(tab, tab + 1, strlen(tab));
1753                 tab[0] = (char) 9;
1754             }
1756             leg_cc = strlen(im->gdes[i].legend);
1757             /* is there a controle code at the end of the legend string ? */
1758             if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1759                 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1760                 leg_cc -= 2;
1761                 im->gdes[i].legend[leg_cc] = '\0';
1762             } else {
1763                 prt_fctn = '\0';
1764             }
1765             /* only valid control codes */
1766             if (prt_fctn != 'l' && prt_fctn != 'n' &&   /* a synonym for l */
1767                 prt_fctn != 'r' &&
1768                 prt_fctn != 'j' &&
1769                 prt_fctn != 'c' &&
1770                 prt_fctn != 'u' &&
1771                 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1772                 free(legspace);
1773                 rrd_set_error
1774                     ("Unknown control code at the end of '%s\\%c'",
1775                      im->gdes[i].legend, prt_fctn);
1776                 return -1;
1777             }
1778             /* \n -> \l */
1779             if (prt_fctn == 'n') {
1780                 prt_fctn = 'l';
1781             }
1783             /* remove exess space from the end of the legend for \g */
1784             while (prt_fctn == 'g' &&
1785                    leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1786                 leg_cc--;
1787                 im->gdes[i].legend[leg_cc] = '\0';
1788             }
1790             if (leg_cc != 0) {
1792                 /* no interleg space if string ends in \g */
1793                 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1794                 if (fill > 0) {
1795                     fill += legspace[i];
1796                 }
1797                 fill +=
1798                     gfx_get_text_width(im,
1799                                        fill + border,
1800                                        im->
1801                                        text_prop
1802                                        [TEXT_PROP_LEGEND].
1803                                        font_desc,
1804                                        im->tabwidth, im->gdes[i].legend);
1805                 leg_c++;
1806             } else {
1807                 legspace[i] = 0;
1808             }
1809             /* who said there was a special tag ... ? */
1810             if (prt_fctn == 'g') {
1811                 prt_fctn = '\0';
1812             }
1814             if (prt_fctn == '\0') {
1815                 if(calc_width && (fill > legendwidth)){
1816                     legendwidth = fill;
1817                 }
1818                 if (i == im->gdes_c - 1 || fill > legendwidth) {
1819                     /* just one legend item is left right or center */
1820                     switch (default_txtalign) {
1821                     case TXA_RIGHT:
1822                         prt_fctn = 'r';
1823                         break;
1824                     case TXA_CENTER:
1825                         prt_fctn = 'c';
1826                         break;
1827                     case TXA_JUSTIFIED:
1828                         prt_fctn = 'j';
1829                         break;
1830                     default:
1831                         prt_fctn = 'l';
1832                         break;
1833                     }
1834                 }
1835                 /* is it time to place the legends ? */
1836                 if (fill > legendwidth) {
1837                     if (leg_c > 1) {
1838                         /* go back one */
1839                         i--;
1840                         fill = fill_last;
1841                         leg_c--;
1842                     }
1843                 }
1844                 if (leg_c == 1 && prt_fctn == 'j') {
1845                     prt_fctn = 'l';
1846                 }
1847             }
1849             if (prt_fctn != '\0') {
1850                 leg_x = border;
1851                 if (leg_c >= 2 && prt_fctn == 'j') {
1852                     glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1853                 } else {
1854                     glue = 0;
1855                 }
1856                 if (prt_fctn == 'c')
1857                     leg_x = border + (double)(legendwidth - fill) / 2.0;
1858                 if (prt_fctn == 'r')
1859                     leg_x = legendwidth - fill + border;
1860                 for (ii = mark; ii <= i; ii++) {
1861                     if (im->gdes[ii].legend[0] == '\0')
1862                         continue;   /* skip empty legends */
1863                     im->gdes[ii].leg_x = leg_x;
1864                     im->gdes[ii].leg_y = leg_y + border;
1865                     leg_x +=
1866                         (double)gfx_get_text_width(im, leg_x,
1867                                            im->
1868                                            text_prop
1869                                            [TEXT_PROP_LEGEND].
1870                                            font_desc,
1871                                            im->tabwidth, im->gdes[ii].legend)
1872                         +(double)legspace[ii]
1873                         + glue;
1874                 }
1875                 leg_y_prev = leg_y;
1876                 if (leg_x > border || prt_fctn == 's')
1877                     leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1878                 if (prt_fctn == 's')
1879                     leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1880                 if (prt_fctn == 'u')
1881                     leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1883                 if(calc_width && (fill > legendwidth)){
1884                     legendwidth = fill;
1885                 }
1886                 fill = 0;
1887                 leg_c = 0;
1888                 mark = ii;
1889             }
1891             if(calc_width){
1892                 strcpy(im->gdes[i].legend, saved_legend);
1893             }
1894         }
1896         if(calc_width){
1897             im->legendwidth = legendwidth + 2 * border;
1898         }
1899         else{
1900             im->legendheight = leg_y + border * 0.6;
1901         }
1902         free(legspace);
1903     }
1904     return 0;
1907 /* create a grid on the graph. it determines what to do
1908    from the values of xsize, start and end */
1910 /* the xaxis labels are determined from the number of seconds per pixel
1911    in the requested graph */
1913 int calc_horizontal_grid(
1914     image_desc_t
1915     *im)
1917     double    range;
1918     double    scaledrange;
1919     int       pixel, i;
1920     int       gridind = 0;
1921     int       decimals, fractionals;
1923     im->ygrid_scale.labfact = 2;
1924     range = im->maxval - im->minval;
1925     scaledrange = range / im->magfact;
1926     /* does the scale of this graph make it impossible to put lines
1927        on it? If so, give up. */
1928     if (isnan(scaledrange)) {
1929         return 0;
1930     }
1932     /* find grid spaceing */
1933     pixel = 1;
1934     if (isnan(im->ygridstep)) {
1935         if (im->extra_flags & ALTYGRID) {
1936             /* find the value with max number of digits. Get number of digits */
1937             decimals =
1938                 ceil(log10
1939                      (max(fabs(im->maxval), fabs(im->minval)) *
1940                       im->viewfactor / im->magfact));
1941             if (decimals <= 0)  /* everything is small. make place for zero */
1942                 decimals = 1;
1943             im->ygrid_scale.gridstep =
1944                 pow((double) 10,
1945                     floor(log10(range * im->viewfactor / im->magfact))) /
1946                 im->viewfactor * im->magfact;
1947             if (im->ygrid_scale.gridstep == 0)  /* range is one -> 0.1 is reasonable scale */
1948                 im->ygrid_scale.gridstep = 0.1;
1949             /* should have at least 5 lines but no more then 15 */
1950             if (range / im->ygrid_scale.gridstep < 5
1951                 && im->ygrid_scale.gridstep >= 30)
1952                 im->ygrid_scale.gridstep /= 10;
1953             if (range / im->ygrid_scale.gridstep > 15)
1954                 im->ygrid_scale.gridstep *= 10;
1955             if (range / im->ygrid_scale.gridstep > 5) {
1956                 im->ygrid_scale.labfact = 1;
1957                 if (range / im->ygrid_scale.gridstep > 8
1958                     || im->ygrid_scale.gridstep <
1959                     1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1960                     im->ygrid_scale.labfact = 2;
1961             } else {
1962                 im->ygrid_scale.gridstep /= 5;
1963                 im->ygrid_scale.labfact = 5;
1964             }
1965             fractionals =
1966                 floor(log10
1967                       (im->ygrid_scale.gridstep *
1968                        (double) im->ygrid_scale.labfact * im->viewfactor /
1969                        im->magfact));
1970             if (fractionals < 0) {  /* small amplitude. */
1971                 int       len = decimals - fractionals + 1;
1973                 if (im->unitslength < len + 2)
1974                     im->unitslength = len + 2;
1975                 sprintf(im->ygrid_scale.labfmt,
1976                         "%%%d.%df%s", len,
1977                         -fractionals, (im->symbol != ' ' ? " %c" : ""));
1978             } else {
1979                 int       len = decimals + 1;
1981                 if (im->unitslength < len + 2)
1982                     im->unitslength = len + 2;
1983                 sprintf(im->ygrid_scale.labfmt,
1984                         "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1985             }
1986         } else {        /* classic rrd grid */
1987             for (i = 0; ylab[i].grid > 0; i++) {
1988                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1989                 gridind = i;
1990                 if (pixel >= 5)
1991                     break;
1992             }
1994             for (i = 0; i < 4; i++) {
1995                 if (pixel * ylab[gridind].lfac[i] >=
1996                     1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1997                     im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1998                     break;
1999                 }
2000             }
2002             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
2003         }
2004     } else {
2005         im->ygrid_scale.gridstep = im->ygridstep;
2006         im->ygrid_scale.labfact = im->ylabfact;
2007     }
2008     return 1;
2011 int draw_horizontal_grid(
2012     image_desc_t
2013     *im)
2015     int       i;
2016     double    scaledstep;
2017     char      graph_label[100];
2018     int       nlabels = 0;
2019     double    X0 = im->xorigin;
2020     double    X1 = im->xorigin + im->xsize;
2021     int       sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2022     int       egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2023     double    MaxY;
2024     double second_axis_magfact = 0;
2025     char *second_axis_symb = "";
2027     scaledstep =
2028         im->ygrid_scale.gridstep /
2029         (double) im->magfact * (double) im->viewfactor;
2030     MaxY = scaledstep * (double) egrid;
2031     for (i = sgrid; i <= egrid; i++) {
2032         double    Y0 = ytr(im,
2033                            im->ygrid_scale.gridstep * i);
2034         double    YN = ytr(im,
2035                            im->ygrid_scale.gridstep * (i + 1));
2037         if (floor(Y0 + 0.5) >=
2038             im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2039             /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2040                with the chosen settings. Add a label if required by settings, or if
2041                there is only one label so far and the next grid line is out of bounds. */
2042             if (i % im->ygrid_scale.labfact == 0
2043                 || (nlabels == 1
2044                     && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2045                 if (im->symbol == ' ') {
2046                     if (im->extra_flags & ALTYGRID) {
2047                         sprintf(graph_label,
2048                                 im->ygrid_scale.labfmt,
2049                                 scaledstep * (double) i);
2050                     } else {
2051                         if (MaxY < 10) {
2052                             sprintf(graph_label, "%4.1f",
2053                                     scaledstep * (double) i);
2054                         } else {
2055                             sprintf(graph_label, "%4.0f",
2056                                     scaledstep * (double) i);
2057                         }
2058                     }
2059                 } else {
2060                     char      sisym = (i == 0 ? ' ' : im->symbol);
2062                     if (im->extra_flags & ALTYGRID) {
2063                         sprintf(graph_label,
2064                                 im->ygrid_scale.labfmt,
2065                                 scaledstep * (double) i, sisym);
2066                     } else {
2067                         if (MaxY < 10) {
2068                             sprintf(graph_label, "%4.1f %c",
2069                                     scaledstep * (double) i, sisym);
2070                         } else {
2071                             sprintf(graph_label, "%4.0f %c",
2072                                     scaledstep * (double) i, sisym);
2073                         }
2074                     }
2075                 }
2076                 nlabels++;
2077                 if (im->second_axis_scale != 0){
2078                         char graph_label_right[100];
2079                         double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2080                         if (im->second_axis_format[0] == '\0'){
2081                             if (!second_axis_magfact){
2082                                 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2083                                 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2084                             }
2085                             sval /= second_axis_magfact;
2087                             if(MaxY < 10) {
2088                                 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2089                             } else {
2090                                 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2091                             }
2092                         }
2093                         else {
2094                            sprintf(graph_label_right,im->second_axis_format,sval);
2095                         }
2096                         gfx_text ( im,
2097                                X1+7, Y0,
2098                                im->graph_col[GRC_FONT],
2099                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2100                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2101                                graph_label_right );
2102                 }
2104                 gfx_text(im,
2105                          X0 -
2106                          im->
2107                          text_prop[TEXT_PROP_AXIS].
2108                          size, Y0,
2109                          im->graph_col[GRC_FONT],
2110                          im->
2111                          text_prop[TEXT_PROP_AXIS].
2112                          font_desc,
2113                          im->tabwidth, 0.0,
2114                          GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2115                 gfx_line(im, X0 - 2, Y0, X0, Y0,
2116                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2117                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2118                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2119                 gfx_dashed_line(im, X0 - 2, Y0,
2120                                 X1 + 2, Y0,
2121                                 MGRIDWIDTH,
2122                                 im->
2123                                 graph_col
2124                                 [GRC_MGRID],
2125                                 im->grid_dash_on, im->grid_dash_off);
2126             } else if (!(im->extra_flags & NOMINOR)) {
2127                 gfx_line(im,
2128                          X0 - 2, Y0,
2129                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2130                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2131                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2132                 gfx_dashed_line(im, X0 - 1, Y0,
2133                                 X1 + 1, Y0,
2134                                 GRIDWIDTH,
2135                                 im->
2136                                 graph_col[GRC_GRID],
2137                                 im->grid_dash_on, im->grid_dash_off);
2138             }
2139         }
2140     }
2141     return 1;
2144 /* this is frexp for base 10 */
2145 double    frexp10(
2146     double,
2147     double *);
2148 double frexp10(
2149     double x,
2150     double *e)
2152     double    mnt;
2153     int       iexp;
2155     iexp = floor(log((double)fabs(x)) / log((double)10));
2156     mnt = x / pow(10.0, iexp);
2157     if (mnt >= 10.0) {
2158         iexp++;
2159         mnt = x / pow(10.0, iexp);
2160     }
2161     *e = iexp;
2162     return mnt;
2166 /* logaritmic horizontal grid */
2167 int horizontal_log_grid(
2168     image_desc_t
2169     *im)
2171     double    yloglab[][10] = {
2172         {
2173          1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2174          0.0, 0.0, 0.0}, {
2175                           1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2176                           0.0, 0.0, 0.0}, {
2177                                            1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2178                                            0.0, 0.0, 0.0}, {
2179                                                             1.0, 2.0, 4.0,
2180                                                             6.0, 8.0, 10.,
2181                                                             0.0,
2182                                                             0.0, 0.0, 0.0}, {
2183                                                                              1.0,
2184                                                                              2.0,
2185                                                                              3.0,
2186                                                                              4.0,
2187                                                                              5.0,
2188                                                                              6.0,
2189                                                                              7.0,
2190                                                                              8.0,
2191                                                                              9.0,
2192                                                                              10.},
2193         {
2194          0, 0, 0, 0, 0, 0, 0, 0, 0, 0}  /* last line */
2195     };
2196     int       i, j, val_exp, min_exp;
2197     double    nex;      /* number of decades in data */
2198     double    logscale; /* scale in logarithmic space */
2199     int       exfrac = 1;   /* decade spacing */
2200     int       mid = -1; /* row in yloglab for major grid */
2201     double    mspac;    /* smallest major grid spacing (pixels) */
2202     int       flab;     /* first value in yloglab to use */
2203     double    value, tmp, pre_value;
2204     double    X0, X1, Y0;
2205     char      graph_label[100];
2207     nex = log10(im->maxval / im->minval);
2208     logscale = im->ysize / nex;
2209     /* major spacing for data with high dynamic range */
2210     while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2211         if (exfrac == 1)
2212             exfrac = 3;
2213         else
2214             exfrac += 3;
2215     }
2217     /* major spacing for less dynamic data */
2218     do {
2219         /* search best row in yloglab */
2220         mid++;
2221         for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2222         mspac = logscale * log10(10.0 / yloglab[mid][i]);
2223     }
2224     while (mspac >
2225            2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2226     if (mid)
2227         mid--;
2228     /* find first value in yloglab */
2229     for (flab = 0;
2230          yloglab[mid][flab] < 10
2231          && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2232     if (yloglab[mid][flab] == 10.0) {
2233         tmp += 1.0;
2234         flab = 0;
2235     }
2236     val_exp = tmp;
2237     if (val_exp % exfrac)
2238         val_exp += abs(-val_exp % exfrac);
2239     X0 = im->xorigin;
2240     X1 = im->xorigin + im->xsize;
2241     /* draw grid */
2242     pre_value = DNAN;
2243     while (1) {
2245         value = yloglab[mid][flab] * pow(10.0, val_exp);
2246         if (AlmostEqual2sComplement(value, pre_value, 4))
2247             break;      /* it seems we are not converging */
2248         pre_value = value;
2249         Y0 = ytr(im, value);
2250         if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2251             break;
2252         /* major grid line */
2253         gfx_line(im,
2254                  X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2255         gfx_line(im, X1, Y0, X1 + 2, Y0,
2256                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2257         gfx_dashed_line(im, X0 - 2, Y0,
2258                         X1 + 2, Y0,
2259                         MGRIDWIDTH,
2260                         im->
2261                         graph_col
2262                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2263         /* label */
2264         if (im->extra_flags & FORCE_UNITS_SI) {
2265             int       scale;
2266             double    pvalue;
2267             char      symbol;
2269             scale = floor(val_exp / 3.0);
2270             if (value >= 1.0)
2271                 pvalue = pow(10.0, val_exp % 3);
2272             else
2273                 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2274             pvalue *= yloglab[mid][flab];
2275             if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2276                 && ((scale + si_symbcenter) >= 0))
2277                 symbol = si_symbol[scale + si_symbcenter];
2278             else
2279                 symbol = '?';
2280             sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2281         } else {
2282             sprintf(graph_label, "%3.0e", value);
2283         }
2284         if (im->second_axis_scale != 0){
2285                 char graph_label_right[100];
2286                 double sval = value*im->second_axis_scale+im->second_axis_shift;
2287                 if (im->second_axis_format[0] == '\0'){
2288                         if (im->extra_flags & FORCE_UNITS_SI) {
2289                                 double mfac = 1;
2290                                 char   *symb = "";
2291                                 auto_scale(im,&sval,&symb,&mfac);
2292                                 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2293                         }
2294                         else {
2295                                 sprintf(graph_label_right,"%3.0e", sval);
2296                         }
2297                 }
2298                 else {
2299                       sprintf(graph_label_right,im->second_axis_format,sval,"");
2300                 }
2302                 gfx_text ( im,
2303                                X1+7, Y0,
2304                                im->graph_col[GRC_FONT],
2305                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2306                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2307                                graph_label_right );
2308         }
2310         gfx_text(im,
2311                  X0 -
2312                  im->
2313                  text_prop[TEXT_PROP_AXIS].
2314                  size, Y0,
2315                  im->graph_col[GRC_FONT],
2316                  im->
2317                  text_prop[TEXT_PROP_AXIS].
2318                  font_desc,
2319                  im->tabwidth, 0.0,
2320                  GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2321         /* minor grid */
2322         if (mid < 4 && exfrac == 1) {
2323             /* find first and last minor line behind current major line
2324              * i is the first line and j tha last */
2325             if (flab == 0) {
2326                 min_exp = val_exp - 1;
2327                 for (i = 1; yloglab[mid][i] < 10.0; i++);
2328                 i = yloglab[mid][i - 1] + 1;
2329                 j = 10;
2330             } else {
2331                 min_exp = val_exp;
2332                 i = yloglab[mid][flab - 1] + 1;
2333                 j = yloglab[mid][flab];
2334             }
2336             /* draw minor lines below current major line */
2337             for (; i < j; i++) {
2339                 value = i * pow(10.0, min_exp);
2340                 if (value < im->minval)
2341                     continue;
2342                 Y0 = ytr(im, value);
2343                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2344                     break;
2345                 /* draw lines */
2346                 gfx_line(im,
2347                          X0 - 2, Y0,
2348                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2349                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2350                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2351                 gfx_dashed_line(im, X0 - 1, Y0,
2352                                 X1 + 1, Y0,
2353                                 GRIDWIDTH,
2354                                 im->
2355                                 graph_col[GRC_GRID],
2356                                 im->grid_dash_on, im->grid_dash_off);
2357             }
2358         } else if (exfrac > 1) {
2359             for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2360                 value = pow(10.0, i);
2361                 if (value < im->minval)
2362                     continue;
2363                 Y0 = ytr(im, value);
2364                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2365                     break;
2366                 /* draw lines */
2367                 gfx_line(im,
2368                          X0 - 2, Y0,
2369                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2370                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2371                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2372                 gfx_dashed_line(im, X0 - 1, Y0,
2373                                 X1 + 1, Y0,
2374                                 GRIDWIDTH,
2375                                 im->
2376                                 graph_col[GRC_GRID],
2377                                 im->grid_dash_on, im->grid_dash_off);
2378             }
2379         }
2381         /* next decade */
2382         if (yloglab[mid][++flab] == 10.0) {
2383             flab = 0;
2384             val_exp += exfrac;
2385         }
2386     }
2388     /* draw minor lines after highest major line */
2389     if (mid < 4 && exfrac == 1) {
2390         /* find first and last minor line below current major line
2391          * i is the first line and j tha last */
2392         if (flab == 0) {
2393             min_exp = val_exp - 1;
2394             for (i = 1; yloglab[mid][i] < 10.0; i++);
2395             i = yloglab[mid][i - 1] + 1;
2396             j = 10;
2397         } else {
2398             min_exp = val_exp;
2399             i = yloglab[mid][flab - 1] + 1;
2400             j = yloglab[mid][flab];
2401         }
2403         /* draw minor lines below current major line */
2404         for (; i < j; i++) {
2406             value = i * pow(10.0, min_exp);
2407             if (value < im->minval)
2408                 continue;
2409             Y0 = ytr(im, value);
2410             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2411                 break;
2412             /* draw lines */
2413             gfx_line(im,
2414                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2415             gfx_line(im, X1, Y0, X1 + 2, Y0,
2416                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2417             gfx_dashed_line(im, X0 - 1, Y0,
2418                             X1 + 1, Y0,
2419                             GRIDWIDTH,
2420                             im->
2421                             graph_col[GRC_GRID],
2422                             im->grid_dash_on, im->grid_dash_off);
2423         }
2424     }
2425     /* fancy minor gridlines */
2426     else if (exfrac > 1) {
2427         for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2428             value = pow(10.0, i);
2429             if (value < im->minval)
2430                 continue;
2431             Y0 = ytr(im, value);
2432             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2433                 break;
2434             /* draw lines */
2435             gfx_line(im,
2436                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2437             gfx_line(im, X1, Y0, X1 + 2, Y0,
2438                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2439             gfx_dashed_line(im, X0 - 1, Y0,
2440                             X1 + 1, Y0,
2441                             GRIDWIDTH,
2442                             im->
2443                             graph_col[GRC_GRID],
2444                             im->grid_dash_on, im->grid_dash_off);
2445         }
2446     }
2448     return 1;
2452 void vertical_grid(
2453     image_desc_t *im)
2455     int       xlab_sel; /* which sort of label and grid ? */
2456     time_t    ti, tilab, timajor;
2457     long      factor;
2458     char      graph_label[100];
2459     double    X0, Y0, Y1;   /* points for filled graph and more */
2460     struct tm tm;
2462     /* the type of time grid is determined by finding
2463        the number of seconds per pixel in the graph */
2464     if (im->xlab_user.minsec == -1) {
2465         factor = (im->end - im->start) / im->xsize;
2466         xlab_sel = 0;
2467         while (xlab[xlab_sel + 1].minsec !=
2468                -1 && xlab[xlab_sel + 1].minsec <= factor) {
2469             xlab_sel++;
2470         }               /* pick the last one */
2471         while (xlab[xlab_sel - 1].minsec ==
2472                xlab[xlab_sel].minsec
2473                && xlab[xlab_sel].length > (im->end - im->start)) {
2474             xlab_sel--;
2475         }               /* go back to the smallest size */
2476         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2477         im->xlab_user.gridst = xlab[xlab_sel].gridst;
2478         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2479         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2480         im->xlab_user.labtm = xlab[xlab_sel].labtm;
2481         im->xlab_user.labst = xlab[xlab_sel].labst;
2482         im->xlab_user.precis = xlab[xlab_sel].precis;
2483         im->xlab_user.stst = xlab[xlab_sel].stst;
2484     }
2486     /* y coords are the same for every line ... */
2487     Y0 = im->yorigin;
2488     Y1 = im->yorigin - im->ysize;
2489     /* paint the minor grid */
2490     if (!(im->extra_flags & NOMINOR)) {
2491         for (ti = find_first_time(im->start,
2492                                   im->
2493                                   xlab_user.
2494                                   gridtm,
2495                                   im->
2496                                   xlab_user.
2497                                   gridst),
2498              timajor =
2499              find_first_time(im->start,
2500                              im->xlab_user.
2501                              mgridtm,
2502                              im->xlab_user.
2503                              mgridst);
2504              ti < im->end && ti != -1;
2505              ti =
2506              find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2507             ) {
2508             /* are we inside the graph ? */
2509             if (ti < im->start || ti > im->end)
2510                 continue;
2511             while (timajor < ti && timajor != -1) {
2512                 timajor = find_next_time(timajor,
2513                                          im->
2514                                          xlab_user.
2515                                          mgridtm, im->xlab_user.mgridst);
2516             }
2517             if (timajor == -1) break; /* fail in case of problems with time increments */
2518             if (ti == timajor)
2519                 continue;   /* skip as falls on major grid line */
2520             X0 = xtr(im, ti);
2521             gfx_line(im, X0, Y1 - 2, X0, Y1,
2522                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2523             gfx_line(im, X0, Y0, X0, Y0 + 2,
2524                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2525             gfx_dashed_line(im, X0, Y0 + 1, X0,
2526                             Y1 - 1, GRIDWIDTH,
2527                             im->
2528                             graph_col[GRC_GRID],
2529                             im->grid_dash_on, im->grid_dash_off);
2530         }
2531     }
2533     /* paint the major grid */
2534     for (ti = find_first_time(im->start,
2535                               im->
2536                               xlab_user.
2537                               mgridtm,
2538                               im->
2539                               xlab_user.
2540                               mgridst);
2541          ti < im->end && ti != -1;
2542          ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2543         ) {
2544         /* are we inside the graph ? */
2545         if (ti < im->start || ti > im->end)
2546             continue;
2547         X0 = xtr(im, ti);
2548         gfx_line(im, X0, Y1 - 2, X0, Y1,
2549                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2550         gfx_line(im, X0, Y0, X0, Y0 + 3,
2551                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2552         gfx_dashed_line(im, X0, Y0 + 3, X0,
2553                         Y1 - 2, MGRIDWIDTH,
2554                         im->
2555                         graph_col
2556                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2557     }
2558     /* paint the labels below the graph */
2559     for (ti =
2560          find_first_time(im->start -
2561                          im->xlab_user.
2562                          precis / 2,
2563                          im->xlab_user.
2564                          labtm,
2565                          im->xlab_user.
2566                          labst);
2567          (ti <=
2568          im->end -
2569          im->xlab_user.precis / 2) && ti != -1;
2570          ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2571         ) {
2572         tilab = ti + im->xlab_user.precis / 2;  /* correct time for the label */
2573         /* are we inside the graph ? */
2574         if (tilab < im->start || tilab > im->end)
2575             continue;
2576 #if HAVE_STRFTIME
2577         localtime_r(&tilab, &tm);
2578         strftime(graph_label, 99, im->xlab_user.stst, &tm);
2579 #else
2580 # error "your libc has no strftime I guess we'll abort the exercise here."
2581 #endif
2582         gfx_text(im,
2583                  xtr(im, tilab),
2584                  Y0 + 3,
2585                  im->graph_col[GRC_FONT],
2586                  im->
2587                  text_prop[TEXT_PROP_AXIS].
2588                  font_desc,
2589                  im->tabwidth, 0.0,
2590                  GFX_H_CENTER, GFX_V_TOP, graph_label);
2591     }
2596 void axis_paint(
2597     image_desc_t *im)
2599     /* draw x and y axis */
2600     /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2601        im->xorigin+im->xsize,im->yorigin-im->ysize,
2602        GRIDWIDTH, im->graph_col[GRC_AXIS]);
2604        gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2605        im->xorigin+im->xsize,im->yorigin-im->ysize,
2606        GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2608     gfx_line(im, im->xorigin - 4,
2609              im->yorigin,
2610              im->xorigin + im->xsize +
2611              4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2612     gfx_line(im, im->xorigin,
2613              im->yorigin + 4,
2614              im->xorigin,
2615              im->yorigin - im->ysize -
2616              4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2617     /* arrow for X and Y axis direction */
2618     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 */
2619                  im->graph_col[GRC_ARROW]);
2620     gfx_close_path(im);
2621     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 */
2622                  im->graph_col[GRC_ARROW]);
2623     gfx_close_path(im);
2624     if (im->second_axis_scale != 0){
2625        gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2626                          im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2627                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2628        gfx_new_area ( im,
2629                    im->xorigin+im->xsize-2,  im->yorigin-im->ysize-2,
2630                    im->xorigin+im->xsize+3,  im->yorigin-im->ysize-2,
2631                    im->xorigin+im->xsize,    im->yorigin-im->ysize-7, /* LINEOFFSET */
2632                    im->graph_col[GRC_ARROW]);
2633        gfx_close_path(im);
2634     }
2638 void grid_paint(
2639     image_desc_t *im)
2641     long      i;
2642     int       res = 0;
2643     double    X0, Y0;   /* points for filled graph and more */
2644     struct gfx_color_t water_color;
2646     if (im->draw_3d_border > 0) {
2647             /* draw 3d border */
2648             i = im->draw_3d_border;
2649             gfx_new_area(im, 0, im->yimg,
2650                          i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2651             gfx_add_point(im, im->ximg - i, i);
2652             gfx_add_point(im, im->ximg, 0);
2653             gfx_add_point(im, 0, 0);
2654             gfx_close_path(im);
2655             gfx_new_area(im, i, im->yimg - i,
2656                          im->ximg - i,
2657                          im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2658             gfx_add_point(im, im->ximg, 0);
2659             gfx_add_point(im, im->ximg, im->yimg);
2660             gfx_add_point(im, 0, im->yimg);
2661             gfx_close_path(im);
2662     }
2663     if (im->draw_x_grid == 1)
2664         vertical_grid(im);
2665     if (im->draw_y_grid == 1) {
2666         if (im->logarithmic) {
2667             res = horizontal_log_grid(im);
2668         } else {
2669             res = draw_horizontal_grid(im);
2670         }
2672         /* dont draw horizontal grid if there is no min and max val */
2673         if (!res) {
2674             char     *nodata = "No Data found";
2676             gfx_text(im, im->ximg / 2,
2677                      (2 * im->yorigin -
2678                       im->ysize) / 2,
2679                      im->graph_col[GRC_FONT],
2680                      im->
2681                      text_prop[TEXT_PROP_AXIS].
2682                      font_desc,
2683                      im->tabwidth, 0.0,
2684                      GFX_H_CENTER, GFX_V_CENTER, nodata);
2685         }
2686     }
2688     /* yaxis unit description */
2689     if (im->ylegend[0] != '\0'){
2690         gfx_text(im,
2691                  im->xOriginLegendY+10,
2692                  im->yOriginLegendY,
2693                  im->graph_col[GRC_FONT],
2694                  im->
2695                  text_prop[TEXT_PROP_UNIT].
2696                  font_desc,
2697                  im->tabwidth,
2698                  RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2700     }
2701     if (im->second_axis_legend[0] != '\0'){
2702             gfx_text( im,
2703                   im->xOriginLegendY2+10,
2704                   im->yOriginLegendY2,
2705                   im->graph_col[GRC_FONT],
2706                   im->text_prop[TEXT_PROP_UNIT].font_desc,
2707                   im->tabwidth,
2708                   RRDGRAPH_YLEGEND_ANGLE,
2709                   GFX_H_CENTER, GFX_V_CENTER,
2710                   im->second_axis_legend);
2711     }
2713     /* graph title */
2714     gfx_text(im,
2715              im->xOriginTitle, im->yOriginTitle+6,
2716              im->graph_col[GRC_FONT],
2717              im->
2718              text_prop[TEXT_PROP_TITLE].
2719              font_desc,
2720              im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2721     /* rrdtool 'logo' */
2722     if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2723         water_color = im->graph_col[GRC_FONT];
2724         water_color.alpha = 0.3;
2725         double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2726         gfx_text(im, xpos, 5,
2727                  water_color,
2728                  im->
2729                  text_prop[TEXT_PROP_WATERMARK].
2730                  font_desc, im->tabwidth,
2731                  -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2732     }
2733     /* graph watermark */
2734     if (im->watermark[0] != '\0') {
2735         water_color = im->graph_col[GRC_FONT];
2736         water_color.alpha = 0.3;
2737         gfx_text(im,
2738                  im->ximg / 2, im->yimg - 6,
2739                  water_color,
2740                  im->
2741                  text_prop[TEXT_PROP_WATERMARK].
2742                  font_desc, im->tabwidth, 0,
2743                  GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2744     }
2746     /* graph labels */
2747     if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2748         for (i = 0; i < im->gdes_c; i++) {
2749             if (im->gdes[i].legend[0] == '\0')
2750                 continue;
2751             /* im->gdes[i].leg_y is the bottom of the legend */
2752             X0 = im->xOriginLegend + im->gdes[i].leg_x;
2753             Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2754             gfx_text(im, X0, Y0,
2755                      im->graph_col[GRC_FONT],
2756                      im->
2757                      text_prop
2758                      [TEXT_PROP_LEGEND].font_desc,
2759                      im->tabwidth, 0.0,
2760                      GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2761             /* The legend for GRAPH items starts with "M " to have
2762                enough space for the box */
2763             if (im->gdes[i].gf != GF_PRINT &&
2764                 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2765                 double    boxH, boxV;
2766                 double    X1, Y1;
2768                 boxH = gfx_get_text_width(im, 0,
2769                                           im->
2770                                           text_prop
2771                                           [TEXT_PROP_LEGEND].
2772                                           font_desc,
2773                                           im->tabwidth, "o") * 1.2;
2774                 boxV = boxH;
2775                 /* shift the box up a bit */
2776                 Y0 -= boxV * 0.4;
2778         if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */ 
2779                         cairo_save(im->cr);
2780                         cairo_new_path(im->cr);
2781                         cairo_set_line_width(im->cr, 1.0);
2782                         gfx_line(im,
2783                                 X0, Y0 - boxV / 2,
2784                                 X0 + boxH, Y0 - boxV / 2,
2785                                 1.0, im->gdes[i].col);
2786                         gfx_close_path(im);
2787                 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2788                         cairo_save(im->cr);
2789                         cairo_new_path(im->cr);
2790                         cairo_set_line_width(im->cr, 1.0);
2791                         gfx_line(im,
2792                                 X0 + boxH / 2, Y0,
2793                                 X0 + boxH / 2, Y0 - boxV,
2794                                 1.0, im->gdes[i].col);
2795                         gfx_close_path(im);
2796                 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2797                         cairo_save(im->cr);
2798                         cairo_new_path(im->cr);
2799                         cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2800                         gfx_line(im,
2801                                 X0, Y0,
2802                                 X0 + boxH, Y0 - boxV,
2803                                 im->gdes[i].linewidth, im->gdes[i].col);
2804                         gfx_close_path(im);
2805                 } else {
2806                 /* make sure transparent colors show up the same way as in the graph */
2807                         gfx_new_area(im,
2808                                      X0, Y0 - boxV,
2809                                      X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2810                         gfx_add_point(im, X0 + boxH, Y0 - boxV);
2811                         gfx_close_path(im);
2812                         gfx_new_area(im, X0, Y0 - boxV, X0,
2813                                      Y0, X0 + boxH, Y0, im->gdes[i].col);
2814                         gfx_add_point(im, X0 + boxH, Y0 - boxV);
2815                         gfx_close_path(im);
2816                         cairo_save(im->cr);
2817                         cairo_new_path(im->cr);
2818                         cairo_set_line_width(im->cr, 1.0);
2819                         X1 = X0 + boxH;
2820                         Y1 = Y0 - boxV;
2821                         gfx_line_fit(im, &X0, &Y0);
2822                         gfx_line_fit(im, &X1, &Y1);
2823                         cairo_move_to(im->cr, X0, Y0);
2824                         cairo_line_to(im->cr, X1, Y0);
2825                         cairo_line_to(im->cr, X1, Y1);
2826                         cairo_line_to(im->cr, X0, Y1);
2827                         cairo_close_path(im->cr);
2828                         cairo_set_source_rgba(im->cr,
2829                                               im->graph_col[GRC_FRAME].red,
2830                                               im->graph_col[GRC_FRAME].green,
2831                                               im->graph_col[GRC_FRAME].blue,
2832                                               im->graph_col[GRC_FRAME].alpha);
2833                 }
2834                 if (im->gdes[i].dash) {
2835                     /* make box borders in legend dashed if the graph is dashed */
2836                     double    dashes[] = {
2837                         3.0
2838                     };
2839                     cairo_set_dash(im->cr, dashes, 1, 0.0);
2840                 }
2841                 cairo_stroke(im->cr);
2842                 cairo_restore(im->cr);
2843             }
2844         }
2845     }
2849 /*****************************************************
2850  * lazy check make sure we rely need to create this graph
2851  *****************************************************/
2853 int lazy_check(
2854     image_desc_t *im)
2856     FILE     *fd = NULL;
2857     int       size = 1;
2858     struct stat imgstat;
2860     if (im->lazy == 0)
2861         return 0;       /* no lazy option */
2862     if (strlen(im->graphfile) == 0)
2863         return 0;       /* inmemory option */
2864     if (stat(im->graphfile, &imgstat) != 0)
2865         return 0;       /* can't stat */
2866     /* one pixel in the existing graph is more then what we would
2867        change here ... */
2868     if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2869         return 0;
2870     if ((fd = fopen(im->graphfile, "rb")) == NULL)
2871         return 0;       /* the file does not exist */
2872     switch (im->imgformat) {
2873     case IF_PNG:
2874         size = PngSize(fd, &(im->ximg), &(im->yimg));
2875         break;
2876     default:
2877         size = 1;
2878     }
2879     fclose(fd);
2880     return size;
2884 int graph_size_location(
2885     image_desc_t
2886     *im,
2887     int elements)
2889     /* The actual size of the image to draw is determined from
2890      ** several sources.  The size given on the command line is
2891      ** the graph area but we need more as we have to draw labels
2892      ** and other things outside the graph area. If the option
2893      ** --full-size-mode is selected the size defines the total
2894      ** image size and the size available for the graph is
2895      ** calculated.
2896      */
2898     /** +---+-----------------------------------+
2899      ** | y |...............graph title.........|
2900      ** |   +---+-------------------------------+
2901      ** | a | y |                               |
2902      ** | x |   |                               |
2903      ** | i | a |                               |
2904      ** | s | x |       main graph area         |
2905      ** |   | i |                               |
2906      ** | t | s |                               |
2907      ** | i |   |                               |
2908      ** | t | l |                               |
2909      ** | l | b +-------------------------------+
2910      ** | e | l |       x axis labels           |
2911      ** +---+---+-------------------------------+
2912      ** |....................legends............|
2913      ** +---------------------------------------+
2914      ** |                   watermark           |
2915      ** +---------------------------------------+
2916      */
2918     int       Xvertical = 0, Xvertical2 = 0, Ytitle =
2919         0, Xylabel = 0, Xmain = 0, Ymain =
2920         0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2922     // no legends and no the shall be plotted it's easy
2923     if (im->extra_flags & ONLY_GRAPH) {
2924         im->xorigin = 0;
2925         im->ximg = im->xsize;
2926         im->yimg = im->ysize;
2927         im->yorigin = im->ysize;
2928         xtr(im, 0);
2929         ytr(im, DNAN);
2930         return 0;
2931     }
2933     if(im->watermark[0] != '\0') {
2934         Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2935     }
2937     // calculate the width of the left vertical legend
2938     if (im->ylegend[0] != '\0') {
2939         Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2940     }
2942     // calculate the width of the right vertical legend
2943     if (im->second_axis_legend[0] != '\0') {
2944         Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2945     }
2946     else{
2947         Xvertical2 = Xspacing;
2948     }
2950     if (im->title[0] != '\0') {
2951         /* The title is placed "inbetween" two text lines so it
2952          ** automatically has some vertical spacing.  The horizontal
2953          ** spacing is added here, on each side.
2954          */
2955         /* if necessary, reduce the font size of the title until it fits the image width */
2956         Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2957     }
2958     else{
2959         // we have no title; get a little clearing from the top
2960         Ytitle = Yspacing;
2961     }
2963     if (elements) {
2964         if (im->draw_x_grid) {
2965             // calculate the height of the horizontal labelling
2966             Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2967         }
2968         if (im->draw_y_grid || im->forceleftspace) {
2969             // calculate the width of the vertical labelling
2970             Xylabel =
2971                 gfx_get_text_width(im, 0,
2972                                    im->text_prop[TEXT_PROP_AXIS].font_desc,
2973                                    im->tabwidth, "0") * im->unitslength;
2974         }
2975     }
2977     // add some space to the labelling
2978     Xylabel += Xspacing;
2980     /* If the legend is printed besides the graph the width has to be
2981      ** calculated first. Placing the legend north or south of the
2982      ** graph requires the width calculation first, so the legend is
2983      ** skipped for the moment.
2984      */
2985     im->legendheight = 0;
2986     im->legendwidth = 0;
2987     if (!(im->extra_flags & NOLEGEND)) {
2988         if(im->legendposition == WEST || im->legendposition == EAST){
2989             if (leg_place(im, 1) == -1){
2990                 return -1;
2991             }
2992         }
2993     }
2995     if (im->extra_flags & FULL_SIZE_MODE) {
2997         /* The actual size of the image to draw has been determined by the user.
2998          ** The graph area is the space remaining after accounting for the legend,
2999          ** the watermark, the axis labels, and the title.
3000          */
3001         im->ximg = im->xsize;
3002         im->yimg = im->ysize;
3003         Xmain = im->ximg;
3004         Ymain = im->yimg;
3006         /* Now calculate the total size.  Insert some spacing where
3007            desired.  im->xorigin and im->yorigin need to correspond
3008            with the lower left corner of the main graph area or, if
3009            this one is not set, the imaginary box surrounding the
3010            pie chart area. */
3011         /* Initial size calculation for the main graph area */
3013         Xmain -= Xylabel;// + Xspacing;
3014         if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3015             Xmain -= im->legendwidth;// + Xspacing;
3016         }
3017         if (im->second_axis_scale != 0){
3018             Xmain -= Xylabel;
3019         }
3020         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3021             Xmain -= Xspacing;
3022         }
3024         Xmain -= Xvertical + Xvertical2;
3026         /* limit the remaining space to 0 */
3027         if(Xmain < 1){
3028             Xmain = 1;
3029         }
3030         im->xsize = Xmain;
3032         /* Putting the legend north or south, the height can now be calculated */
3033         if (!(im->extra_flags & NOLEGEND)) {
3034             if(im->legendposition == NORTH || im->legendposition == SOUTH){
3035                 im->legendwidth = im->ximg;
3036                 if (leg_place(im, 0) == -1){
3037                     return -1;
3038                 }
3039             }
3040         }
3042         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3043             Ymain -=  Yxlabel + im->legendheight;
3044         }
3045         else{
3046             Ymain -= Yxlabel;
3047         }
3049         /* reserve space for the title *or* some padding above the graph */
3050         Ymain -= Ytitle;
3052             /* reserve space for padding below the graph */
3053         if (im->extra_flags & NOLEGEND) {
3054             Ymain -= 0.5*Yspacing;
3055         }
3057         if (im->watermark[0] != '\0') {
3058             Ymain -= Ywatermark;
3059         }
3060         /* limit the remaining height to 0 */
3061         if(Ymain < 1){
3062             Ymain = 1;
3063         }
3064         im->ysize = Ymain;
3065     } else {            /* dimension options -width and -height refer to the dimensions of the main graph area */
3067         /* The actual size of the image to draw is determined from
3068          ** several sources.  The size given on the command line is
3069          ** the graph area but we need more as we have to draw labels
3070          ** and other things outside the graph area.
3071          */
3073         if (elements) {
3074             Xmain = im->xsize; // + Xspacing;
3075             Ymain = im->ysize;
3076         }
3078         im->ximg = Xmain + Xylabel;
3079         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3080             im->ximg += Xspacing;
3081         }
3083         if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3084             im->ximg += im->legendwidth;// + Xspacing;
3085         }
3086         if (im->second_axis_scale != 0){
3087             im->ximg += Xylabel;
3088         }
3090         im->ximg += Xvertical + Xvertical2;
3092         if (!(im->extra_flags & NOLEGEND)) {
3093             if(im->legendposition == NORTH || im->legendposition == SOUTH){
3094                 im->legendwidth = im->ximg;
3095                 if (leg_place(im, 0) == -1){
3096                     return -1;
3097                 }
3098             }
3099         }
3101         im->yimg = Ymain + Yxlabel;
3102         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3103              im->yimg += im->legendheight;
3104         }
3106         /* reserve space for the title *or* some padding above the graph */
3107         if (Ytitle) {
3108             im->yimg += Ytitle;
3109         } else {
3110             im->yimg += 1.5 * Yspacing;
3111         }
3112         /* reserve space for padding below the graph */
3113         if (im->extra_flags & NOLEGEND) {
3114             im->yimg += 0.5*Yspacing;
3115         }
3117         if (im->watermark[0] != '\0') {
3118             im->yimg += Ywatermark;
3119         }
3120     }
3123     /* In case of putting the legend in west or east position the first
3124      ** legend calculation might lead to wrong positions if some items
3125      ** are not aligned on the left hand side (e.g. centered) as the
3126      ** legendwidth wight have been increased after the item was placed.
3127      ** In this case the positions have to be recalculated.
3128      */
3129     if (!(im->extra_flags & NOLEGEND)) {
3130         if(im->legendposition == WEST || im->legendposition == EAST){
3131             if (leg_place(im, 0) == -1){
3132                 return -1;
3133             }
3134         }
3135     }
3137     /* After calculating all dimensions
3138      ** it is now possible to calculate
3139      ** all offsets.
3140      */
3141     switch(im->legendposition){
3142         case NORTH:
3143             im->xOriginTitle   = (im->ximg / 2);
3144             im->yOriginTitle   = 0;
3146             im->xOriginLegend  = 0;
3147             im->yOriginLegend  = Ytitle;
3149             im->xOriginLegendY = 0;
3150             im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3152             im->xorigin        = Xvertical + Xylabel;
3153             im->yorigin        = Ytitle + im->legendheight + Ymain;
3155             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3156             if (im->second_axis_scale != 0){
3157                 im->xOriginLegendY2 += Xylabel;
3158             }
3159             im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3161             break;
3163         case WEST:
3164             im->xOriginTitle   = im->legendwidth + im->xsize / 2;
3165             im->yOriginTitle   = 0;
3167             im->xOriginLegend  = 0;
3168             im->yOriginLegend  = Ytitle;
3170             im->xOriginLegendY = im->legendwidth;
3171             im->yOriginLegendY = Ytitle + (Ymain / 2);
3173             im->xorigin        = im->legendwidth + Xvertical + Xylabel;
3174             im->yorigin        = Ytitle + Ymain;
3176             im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3177             if (im->second_axis_scale != 0){
3178                 im->xOriginLegendY2 += Xylabel;
3179             }
3180             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3182             break;
3184         case SOUTH:
3185             im->xOriginTitle   = im->ximg / 2;
3186             im->yOriginTitle   = 0;
3188             im->xOriginLegend  = 0;
3189             im->yOriginLegend  = Ytitle + Ymain + Yxlabel;
3191             im->xOriginLegendY = 0;
3192             im->yOriginLegendY = Ytitle + (Ymain / 2);
3194             im->xorigin        = Xvertical + Xylabel;
3195             im->yorigin        = Ytitle + Ymain;
3197             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3198             if (im->second_axis_scale != 0){
3199                 im->xOriginLegendY2 += Xylabel;
3200             }
3201             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3203             break;
3205         case EAST:
3206             im->xOriginTitle   = im->xsize / 2;
3207             im->yOriginTitle   = 0;
3209             im->xOriginLegend  = Xvertical + Xylabel + Xmain + Xvertical2;
3210             if (im->second_axis_scale != 0){
3211                 im->xOriginLegend += Xylabel;
3212             }
3213             im->yOriginLegend  = Ytitle;
3215             im->xOriginLegendY = 0;
3216             im->yOriginLegendY = Ytitle + (Ymain / 2);
3218             im->xorigin        = Xvertical + Xylabel;
3219             im->yorigin        = Ytitle + Ymain;
3221             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3222             if (im->second_axis_scale != 0){
3223                 im->xOriginLegendY2 += Xylabel;
3224             }
3225             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3227             if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3228                 im->xOriginTitle    += Xspacing;
3229                 im->xOriginLegend   += Xspacing;
3230                 im->xOriginLegendY  += Xspacing;
3231                 im->xorigin         += Xspacing;
3232                 im->xOriginLegendY2 += Xspacing;
3233             }
3234             break;
3235     }
3237     xtr(im, 0);
3238     ytr(im, DNAN);
3239     return 0;
3242 static cairo_status_t cairo_output(
3243     void *closure,
3244     const unsigned char
3245     *data,
3246     unsigned int length)
3248     image_desc_t *im = (image_desc_t*)closure;
3250     im->rendered_image =
3251         (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3252     if (im->rendered_image == NULL)
3253         return CAIRO_STATUS_WRITE_ERROR;
3254     memcpy(im->rendered_image + im->rendered_image_size, data, length);
3255     im->rendered_image_size += length;
3256     return CAIRO_STATUS_SUCCESS;
3259 /* draw that picture thing ... */
3260 int graph_paint(
3261     image_desc_t *im)
3263     int       i, ii;
3264     int       lazy = lazy_check(im);
3265     double    areazero = 0.0;
3266     graph_desc_t *lastgdes = NULL;
3267     rrd_infoval_t info;
3269 //    PangoFontMap *font_map = pango_cairo_font_map_get_default();
3271     /* pull the data from the rrd files ... */
3272     if (data_fetch(im) == -1)
3273         return -1;
3274     /* evaluate VDEF and CDEF operations ... */
3275     if (data_calc(im) == -1)
3276         return -1;
3277     /* calculate and PRINT and GPRINT definitions. We have to do it at
3278      * this point because it will affect the length of the legends
3279      * if there are no graph elements (i==0) we stop here ...
3280      * if we are lazy, try to quit ...
3281      */
3282     i = print_calc(im);
3283     if (i < 0)
3284         return -1;
3286     /* if we want and can be lazy ... quit now */
3287     if (i == 0)
3288         return 0;
3290 /**************************************************************
3291  *** Calculating sizes and locations became a bit confusing ***
3292  *** so I moved this into a separate function.              ***
3293  **************************************************************/
3294     if (graph_size_location(im, i) == -1)
3295         return -1;
3297     info.u_cnt = im->xorigin;
3298     grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3299     info.u_cnt = im->yorigin - im->ysize;
3300     grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3301     info.u_cnt = im->xsize;
3302     grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3303     info.u_cnt = im->ysize;
3304     grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3305     info.u_cnt = im->ximg;
3306     grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3307     info.u_cnt = im->yimg;
3308     grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3309     info.u_cnt = im->start;
3310     grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3311     info.u_cnt = im->end;
3312     grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3314     /* if we want and can be lazy ... quit now */
3315     if (lazy)
3316         return 0;
3318     /* get actual drawing data and find min and max values */
3319     if (data_proc(im) == -1)
3320         return -1;
3321     if (!im->logarithmic) {
3322         si_unit(im);
3323     }
3325     /* identify si magnitude Kilo, Mega Giga ? */
3326     if (!im->rigid && !im->logarithmic)
3327         expand_range(im);   /* make sure the upper and lower limit are
3328                                sensible values */
3330     info.u_val = im->minval;
3331     grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3332     info.u_val = im->maxval;
3333     grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3336     if (!calc_horizontal_grid(im))
3337         return -1;
3338     /* reset precalc */
3339     ytr(im, DNAN);
3340 /*   if (im->gridfit)
3341      apply_gridfit(im); */
3342     /* the actual graph is created by going through the individual
3343        graph elements and then drawing them */
3344     cairo_surface_destroy(im->surface);
3345     switch (im->imgformat) {
3346     case IF_PNG:
3347         im->surface =
3348             cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3349                                        im->ximg * im->zoom,
3350                                        im->yimg * im->zoom);
3351         break;
3352     case IF_PDF:
3353         im->gridfit = 0;
3354         im->surface = strlen(im->graphfile)
3355             ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3356                                        im->yimg * im->zoom)
3357             : cairo_pdf_surface_create_for_stream
3358             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3359         break;
3360     case IF_EPS:
3361         im->gridfit = 0;
3362         im->surface = strlen(im->graphfile)
3363             ?
3364             cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3365                                     im->yimg * im->zoom)
3366             : cairo_ps_surface_create_for_stream
3367             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3368         break;
3369     case IF_SVG:
3370         im->gridfit = 0;
3371         im->surface = strlen(im->graphfile)
3372             ?
3373             cairo_svg_surface_create(im->
3374                                      graphfile,
3375                                      im->ximg * im->zoom, im->yimg * im->zoom)
3376             : cairo_svg_surface_create_for_stream
3377             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3378         cairo_svg_surface_restrict_to_version
3379             (im->surface, CAIRO_SVG_VERSION_1_1);
3380         break;
3381     };
3382     cairo_destroy(im->cr);
3383     im->cr = cairo_create(im->surface);
3384     cairo_set_antialias(im->cr, im->graph_antialias);
3385     cairo_scale(im->cr, im->zoom, im->zoom);
3386 //    pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3387     gfx_new_area(im, 0, 0, 0, im->yimg,
3388                  im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3389     gfx_add_point(im, im->ximg, 0);
3390     gfx_close_path(im);
3391     gfx_new_area(im, im->xorigin,
3392                  im->yorigin,
3393                  im->xorigin +
3394                  im->xsize, im->yorigin,
3395                  im->xorigin +
3396                  im->xsize,
3397                  im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3398     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3399     gfx_close_path(im);
3400     cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3401                     im->xsize, im->ysize + 2.0);
3402     cairo_clip(im->cr);
3403     if (im->minval > 0.0)
3404         areazero = im->minval;
3405     if (im->maxval < 0.0)
3406         areazero = im->maxval;
3407     for (i = 0; i < im->gdes_c; i++) {
3408         switch (im->gdes[i].gf) {
3409         case GF_CDEF:
3410         case GF_VDEF:
3411         case GF_DEF:
3412         case GF_PRINT:
3413         case GF_GPRINT:
3414         case GF_COMMENT:
3415         case GF_TEXTALIGN:
3416         case GF_HRULE:
3417         case GF_VRULE:
3418         case GF_XPORT:
3419         case GF_SHIFT:
3420             break;
3421         case GF_TICK:
3422             for (ii = 0; ii < im->xsize; ii++) {
3423                 if (!isnan(im->gdes[i].p_data[ii])
3424                     && im->gdes[i].p_data[ii] != 0.0) {
3425                     if (im->gdes[i].yrule > 0) {
3426                         gfx_line(im,
3427                                  im->xorigin + ii,
3428                                  im->yorigin + 1.0,
3429                                  im->xorigin + ii,
3430                                  im->yorigin -
3431                                  im->gdes[i].yrule *
3432                                  im->ysize, 1.0, im->gdes[i].col);
3433                     } else if (im->gdes[i].yrule < 0) {
3434                         gfx_line(im,
3435                                  im->xorigin + ii,
3436                                  im->yorigin - im->ysize - 1.0,
3437                                  im->xorigin + ii,
3438                                  im->yorigin - im->ysize -
3439                                                 im->gdes[i].
3440                                                 yrule *
3441                                  im->ysize, 1.0, im->gdes[i].col);
3442                     }
3443                 }
3444             }
3445             break;
3446         case GF_LINE:
3447         case GF_AREA: {
3448             rrd_value_t diffval = im->maxval - im->minval;
3449             rrd_value_t maxlimit = im->maxval + 9 * diffval;
3450             rrd_value_t minlimit = im->minval - 9 * diffval;        
3451             for (ii = 0; ii < im->xsize; ii++) {
3452                /* fix data points at oo and -oo */
3453                 if (isinf(im->gdes[i].p_data[ii])) {
3454                     if (im->gdes[i].p_data[ii] > 0) {
3455                         im->gdes[i].p_data[ii] = im->maxval;
3456                     } else {
3457                         im->gdes[i].p_data[ii] = im->minval;
3458                     }
3459                 }
3460                 /* some versions of cairo go unstable when trying
3461                    to draw way out of the canvas ... lets not even try */
3462                 if (im->gdes[i].p_data[ii] > maxlimit) {
3463                     im->gdes[i].p_data[ii] = maxlimit;
3464                 }
3465                 if (im->gdes[i].p_data[ii] < minlimit) {
3466                     im->gdes[i].p_data[ii] = minlimit;
3467                 }
3468             }           /* for */
3470             /* *******************************************************
3471                a           ___. (a,t)
3472                |   |    ___
3473                ____|   |   |   |
3474                |       |___|
3475                -------|--t-1--t--------------------------------
3477                if we know the value at time t was a then
3478                we draw a square from t-1 to t with the value a.
3480                ********************************************************* */
3481             if (im->gdes[i].col.alpha != 0.0) {
3482                 /* GF_LINE and friend */
3483                 if (im->gdes[i].gf == GF_LINE) {
3484                     double    last_y = 0.0;
3485                     int       draw_on = 0;
3487                     cairo_save(im->cr);
3488                     cairo_new_path(im->cr);
3489                     cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3490                     if (im->gdes[i].dash) {
3491                         cairo_set_dash(im->cr,
3492                                        im->gdes[i].p_dashes,
3493                                        im->gdes[i].ndash, im->gdes[i].offset);
3494                     }
3496                     for (ii = 1; ii < im->xsize; ii++) {
3497                         if (isnan(im->gdes[i].p_data[ii])
3498                             || (im->slopemode == 1
3499                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3500                             draw_on = 0;
3501                             continue;
3502                         }
3503                         if (draw_on == 0) {
3504                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3505                             if (im->slopemode == 0) {
3506                                 double    x = ii - 1 + im->xorigin;
3507                                 double    y = last_y;
3509                                 gfx_line_fit(im, &x, &y);
3510                                 cairo_move_to(im->cr, x, y);
3511                                 x = ii + im->xorigin;
3512                                 y = last_y;
3513                                 gfx_line_fit(im, &x, &y);
3514                                 cairo_line_to(im->cr, x, y);
3515                             } else {
3516                                 double    x = ii - 1 + im->xorigin;
3517                                 double    y =
3518                                     ytr(im, im->gdes[i].p_data[ii - 1]);
3519                                 gfx_line_fit(im, &x, &y);
3520                                 cairo_move_to(im->cr, x, y);
3521                                 x = ii + im->xorigin;
3522                                 y = last_y;
3523                                 gfx_line_fit(im, &x, &y);
3524                                 cairo_line_to(im->cr, x, y);
3525                             }
3526                             draw_on = 1;
3527                         } else {
3528                             double    x1 = ii + im->xorigin;
3529                             double    y1 = ytr(im, im->gdes[i].p_data[ii]);
3531                             if (im->slopemode == 0
3532                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3533                                 double    x = ii - 1 + im->xorigin;
3534                                 double    y = y1;
3536                                 gfx_line_fit(im, &x, &y);
3537                                 cairo_line_to(im->cr, x, y);
3538                             };
3539                             last_y = y1;
3540                             gfx_line_fit(im, &x1, &y1);
3541                             cairo_line_to(im->cr, x1, y1);
3542                         };
3543                     }
3544                     cairo_set_source_rgba(im->cr,
3545                                           im->gdes[i].
3546                                           col.red,
3547                                           im->gdes[i].
3548                                           col.green,
3549                                           im->gdes[i].
3550                                           col.blue, im->gdes[i].col.alpha);
3551                     cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3552                     cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3553                     cairo_stroke(im->cr);
3554                     cairo_restore(im->cr);
3555                 } else {
3556                     int       idxI = -1;
3557                     double   *foreY =
3558                         (double *) malloc(sizeof(double) * im->xsize * 2);
3559                     double   *foreX =
3560                         (double *) malloc(sizeof(double) * im->xsize * 2);
3561                     double   *backY =
3562                         (double *) malloc(sizeof(double) * im->xsize * 2);
3563                     double   *backX =
3564                         (double *) malloc(sizeof(double) * im->xsize * 2);
3565                     int       drawem = 0;
3567                     for (ii = 0; ii <= im->xsize; ii++) {
3568                         double    ybase, ytop;
3570                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3571                             int       cntI = 1;
3572                             int       lastI = 0;
3574                             while (cntI < idxI
3575                                    &&
3576                                    AlmostEqual2sComplement(foreY
3577                                                            [lastI],
3578                                                            foreY[cntI], 4)
3579                                    &&
3580                                    AlmostEqual2sComplement(foreY
3581                                                            [lastI],
3582                                                            foreY
3583                                                            [cntI + 1], 4)) {
3584                                 cntI++;
3585                             }
3586                             gfx_new_area(im,
3587                                          backX[0], backY[0],
3588                                          foreX[0], foreY[0],
3589                                          foreX[cntI],
3590                                          foreY[cntI], im->gdes[i].col);
3591                             while (cntI < idxI) {
3592                                 lastI = cntI;
3593                                 cntI++;
3594                                 while (cntI < idxI
3595                                        &&
3596                                        AlmostEqual2sComplement(foreY
3597                                                                [lastI],
3598                                                                foreY[cntI], 4)
3599                                        &&
3600                                        AlmostEqual2sComplement(foreY
3601                                                                [lastI],
3602                                                                foreY
3603                                                                [cntI
3604                                                                 + 1], 4)) {
3605                                     cntI++;
3606                                 }
3607                                 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3608                             }
3609                             gfx_add_point(im, backX[idxI], backY[idxI]);
3610                             while (idxI > 1) {
3611                                 lastI = idxI;
3612                                 idxI--;
3613                                 while (idxI > 1
3614                                        &&
3615                                        AlmostEqual2sComplement(backY
3616                                                                [lastI],
3617                                                                backY[idxI], 4)
3618                                        &&
3619                                        AlmostEqual2sComplement(backY
3620                                                                [lastI],
3621                                                                backY
3622                                                                [idxI
3623                                                                 - 1], 4)) {
3624                                     idxI--;
3625                                 }
3626                                 gfx_add_point(im, backX[idxI], backY[idxI]);
3627                             }
3628                             idxI = -1;
3629                             drawem = 0;
3630                             gfx_close_path(im);
3631                         }
3632                         if (drawem != 0) {
3633                             drawem = 0;
3634                             idxI = -1;
3635                         }
3636                         if (ii == im->xsize)
3637                             break;
3638                         if (im->slopemode == 0 && ii == 0) {
3639                             continue;
3640                         }
3641                         if (isnan(im->gdes[i].p_data[ii])) {
3642                             drawem = 1;
3643                             continue;
3644                         }
3645                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3646                         if (lastgdes && im->gdes[i].stack) {
3647                             ybase = ytr(im, lastgdes->p_data[ii]);
3648                         } else {
3649                             ybase = ytr(im, areazero);
3650                         }
3651                         if (ybase == ytop) {
3652                             drawem = 1;
3653                             continue;
3654                         }
3656                         if (ybase > ytop) {
3657                             double    extra = ytop;
3659                             ytop = ybase;
3660                             ybase = extra;
3661                         }
3662                         if (im->slopemode == 0) {
3663                             backY[++idxI] = ybase - 0.2;
3664                             backX[idxI] = ii + im->xorigin - 1;
3665                             foreY[idxI] = ytop + 0.2;
3666                             foreX[idxI] = ii + im->xorigin - 1;
3667                         }
3668                         backY[++idxI] = ybase - 0.2;
3669                         backX[idxI] = ii + im->xorigin;
3670                         foreY[idxI] = ytop + 0.2;
3671                         foreX[idxI] = ii + im->xorigin;
3672                     }
3673                     /* close up any remaining area */
3674                     free(foreY);
3675                     free(foreX);
3676                     free(backY);
3677                     free(backX);
3678                 }       /* else GF_LINE */
3679             }
3680             /* if color != 0x0 */
3681             /* make sure we do not run into trouble when stacking on NaN */
3682             for (ii = 0; ii < im->xsize; ii++) {
3683                 if (isnan(im->gdes[i].p_data[ii])) {
3684                     if (lastgdes && (im->gdes[i].stack)) {
3685                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3686                     } else {
3687                         im->gdes[i].p_data[ii] = areazero;
3688                     }
3689                 }
3690             }
3691             lastgdes = &(im->gdes[i]);
3692             break;
3693         } /* GF_AREA, GF_LINE, GF_GRAD */
3694         case GF_STACK:
3695             rrd_set_error
3696                 ("STACK should already be turned into LINE or AREA here");
3697             return -1;
3698             break;
3699         }               /* switch */
3700     }
3701     cairo_reset_clip(im->cr);
3703     /* grid_paint also does the text */
3704     if (!(im->extra_flags & ONLY_GRAPH))
3705         grid_paint(im);
3706     if (!(im->extra_flags & ONLY_GRAPH))
3707         axis_paint(im);
3708     /* the RULES are the last thing to paint ... */
3709     for (i = 0; i < im->gdes_c; i++) {
3711         switch (im->gdes[i].gf) {
3712         case GF_HRULE:
3713             if (im->gdes[i].yrule >= im->minval
3714                 && im->gdes[i].yrule <= im->maxval) {
3715                 cairo_save(im->cr);
3716                 if (im->gdes[i].dash) {
3717                     cairo_set_dash(im->cr,
3718                                    im->gdes[i].p_dashes,
3719                                    im->gdes[i].ndash, im->gdes[i].offset);
3720                 }
3721                 gfx_line(im, im->xorigin,
3722                          ytr(im, im->gdes[i].yrule),
3723                          im->xorigin + im->xsize,
3724                          ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3725                 cairo_stroke(im->cr);
3726                 cairo_restore(im->cr);
3727             }
3728             break;
3729         case GF_VRULE:
3730             if (im->gdes[i].xrule >= im->start
3731                 && im->gdes[i].xrule <= im->end) {
3732                 cairo_save(im->cr);
3733                 if (im->gdes[i].dash) {
3734                     cairo_set_dash(im->cr,
3735                                    im->gdes[i].p_dashes,
3736                                    im->gdes[i].ndash, im->gdes[i].offset);
3737                 }
3738                 gfx_line(im,
3739                          xtr(im, im->gdes[i].xrule),
3740                          im->yorigin, xtr(im,
3741                                           im->
3742                                           gdes[i].
3743                                           xrule),
3744                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3745                 cairo_stroke(im->cr);
3746                 cairo_restore(im->cr);
3747             }
3748             break;
3749         default:
3750             break;
3751         }
3752     }
3755     switch (im->imgformat) {
3756     case IF_PNG:
3757     {
3758         cairo_status_t status;
3760         status = strlen(im->graphfile) ?
3761             cairo_surface_write_to_png(im->surface, im->graphfile)
3762             : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3763                                                 im);
3765         if (status != CAIRO_STATUS_SUCCESS) {
3766             rrd_set_error("Could not save png to '%s'", im->graphfile);
3767             return 1;
3768         }
3769         break;
3770     }
3771     default:
3772         if (strlen(im->graphfile)) {
3773             cairo_show_page(im->cr);
3774         } else {
3775             cairo_surface_finish(im->surface);
3776         }
3777         break;
3778     }
3780     return 0;
3784 /*****************************************************
3785  * graph stuff
3786  *****************************************************/
3788 int gdes_alloc(
3789     image_desc_t *im)
3792     im->gdes_c++;
3793     if ((im->gdes = (graph_desc_t *)
3794          rrd_realloc(im->gdes, (im->gdes_c)
3795                      * sizeof(graph_desc_t))) == NULL) {
3796         rrd_set_error("realloc graph_descs");
3797         return -1;
3798     }
3801     im->gdes[im->gdes_c - 1].step = im->step;
3802     im->gdes[im->gdes_c - 1].step_orig = im->step;
3803     im->gdes[im->gdes_c - 1].stack = 0;
3804     im->gdes[im->gdes_c - 1].linewidth = 0;
3805     im->gdes[im->gdes_c - 1].debug = 0;
3806     im->gdes[im->gdes_c - 1].start = im->start;
3807     im->gdes[im->gdes_c - 1].start_orig = im->start;
3808     im->gdes[im->gdes_c - 1].end = im->end;
3809     im->gdes[im->gdes_c - 1].end_orig = im->end;
3810     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3811     im->gdes[im->gdes_c - 1].data = NULL;
3812     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3813     im->gdes[im->gdes_c - 1].data_first = 0;
3814     im->gdes[im->gdes_c - 1].p_data = NULL;
3815     im->gdes[im->gdes_c - 1].rpnp = NULL;
3816     im->gdes[im->gdes_c - 1].p_dashes = NULL;
3817     im->gdes[im->gdes_c - 1].shift = 0.0;
3818     im->gdes[im->gdes_c - 1].dash = 0;
3819     im->gdes[im->gdes_c - 1].ndash = 0;
3820     im->gdes[im->gdes_c - 1].offset = 0;
3821     im->gdes[im->gdes_c - 1].col.red = 0.0;
3822     im->gdes[im->gdes_c - 1].col.green = 0.0;
3823     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3824     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3825     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3826     im->gdes[im->gdes_c - 1].format[0] = '\0';
3827     im->gdes[im->gdes_c - 1].strftm = 0;
3828     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3829     im->gdes[im->gdes_c - 1].ds = -1;
3830     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3831     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3832     im->gdes[im->gdes_c - 1].yrule = DNAN;
3833     im->gdes[im->gdes_c - 1].xrule = 0;
3834     return 0;
3837 /* copies input untill the first unescaped colon is found
3838    or until input ends. backslashes have to be escaped as well */
3839 int scan_for_col(
3840     const char *const input,
3841     int len,
3842     char *const output)
3844     int       inp, outp = 0;
3846     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3847         if (input[inp] == '\\'
3848             && input[inp + 1] != '\0'
3849             && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3850             output[outp++] = input[++inp];
3851         } else {
3852             output[outp++] = input[inp];
3853         }
3854     }
3855     output[outp] = '\0';
3856     return inp;
3859 /* Now just a wrapper around rrd_graph_v */
3860 int rrd_graph(
3861     int argc,
3862     char **argv,
3863     char ***prdata,
3864     int *xsize,
3865     int *ysize,
3866     FILE * stream,
3867     double *ymin,
3868     double *ymax)
3870     int       prlines = 0;
3871     rrd_info_t *grinfo = NULL;
3872     rrd_info_t *walker;
3874     grinfo = rrd_graph_v(argc, argv);
3875     if (grinfo == NULL)
3876         return -1;
3877     walker = grinfo;
3878     (*prdata) = NULL;
3879     while (walker) {
3880         if (strcmp(walker->key, "image_info") == 0) {
3881             prlines++;
3882             if (((*prdata) =
3883                  (char**)rrd_realloc((*prdata),
3884                              (prlines + 1) * sizeof(char *))) == NULL) {
3885                 rrd_set_error("realloc prdata");
3886                 return 0;
3887             }
3888             /* imginfo goes to position 0 in the prdata array */
3889             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3890                                              + 2) * sizeof(char));
3891             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3892             (*prdata)[prlines] = NULL;
3893         }
3894         /* skip anything else */
3895         walker = walker->next;
3896     }
3897     walker = grinfo;
3898     *xsize = 0;
3899     *ysize = 0;
3900     *ymin = 0;
3901     *ymax = 0;
3902     while (walker) {
3903         if (strcmp(walker->key, "image_width") == 0) {
3904             *xsize = walker->value.u_cnt;
3905         } else if (strcmp(walker->key, "image_height") == 0) {
3906             *ysize = walker->value.u_cnt;
3907         } else if (strcmp(walker->key, "value_min") == 0) {
3908             *ymin = walker->value.u_val;
3909         } else if (strcmp(walker->key, "value_max") == 0) {
3910             *ymax = walker->value.u_val;
3911         } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3912             prlines++;
3913             if (((*prdata) =
3914                  (char**)rrd_realloc((*prdata),
3915                              (prlines + 1) * sizeof(char *))) == NULL) {
3916                 rrd_set_error("realloc prdata");
3917                 return 0;
3918             }
3919             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3920                                              + 2) * sizeof(char));
3921             (*prdata)[prlines] = NULL;
3922             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3923         } else if (strcmp(walker->key, "image") == 0) {
3924             if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3925                    (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3926                 rrd_set_error("writing image");
3927                 return 0;
3928             }
3929         }
3930         /* skip anything else */
3931         walker = walker->next;
3932     }
3933     rrd_info_free(grinfo);
3934     return 0;
3938 /* Some surgery done on this function, it became ridiculously big.
3939 ** Things moved:
3940 ** - initializing     now in rrd_graph_init()
3941 ** - options parsing  now in rrd_graph_options()
3942 ** - script parsing   now in rrd_graph_script()
3943 */
3944 rrd_info_t *rrd_graph_v(
3945     int argc,
3946     char **argv)
3948     image_desc_t im;
3949     rrd_info_t *grinfo;
3950     char *old_locale;
3951     rrd_graph_init(&im);
3952     /* a dummy surface so that we can measure text sizes for placements */
3953     old_locale = setlocale(LC_NUMERIC, NULL);
3954     setlocale(LC_NUMERIC, "C");
3955     rrd_graph_options(argc, argv, &im);
3956     if (rrd_test_error()) {
3957         rrd_info_free(im.grinfo);
3958         im_free(&im);
3959         return NULL;
3960     }
3962     if (optind >= argc) {
3963         rrd_info_free(im.grinfo);
3964         im_free(&im);
3965         rrd_set_error("missing filename");
3966         return NULL;
3967     }
3969     if (strlen(argv[optind]) >= MAXPATH) {
3970         rrd_set_error("filename (including path) too long");
3971         rrd_info_free(im.grinfo);
3972         im_free(&im);
3973         return NULL;
3974     }
3976     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3977     im.graphfile[MAXPATH - 1] = '\0';
3979     if (strcmp(im.graphfile, "-") == 0) {
3980         im.graphfile[0] = '\0';
3981     }
3983     rrd_graph_script(argc, argv, &im, 1);
3984     setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
3986     if (rrd_test_error()) {
3987         rrd_info_free(im.grinfo);
3988         im_free(&im);
3989         return NULL;
3990     }
3992     /* Everything is now read and the actual work can start */
3994     if (graph_paint(&im) == -1) {
3995         rrd_info_free(im.grinfo);
3996         im_free(&im);
3997         return NULL;
3998     }
4001     /* The image is generated and needs to be output.
4002      ** Also, if needed, print a line with information about the image.
4003      */
4005     if (im.imginfo) {
4006         rrd_infoval_t info;
4007         char     *path;
4008         char     *filename;
4010         path = strdup(im.graphfile);
4011         filename = basename(path);
4012         info.u_str =
4013             sprintf_alloc(im.imginfo,
4014                           filename,
4015                           (long) (im.zoom *
4016                                   im.ximg), (long) (im.zoom * im.yimg));
4017         grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
4018         free(info.u_str);
4019         free(path);
4020     }
4021     if (im.rendered_image) {
4022         rrd_infoval_t img;
4024         img.u_blo.size = im.rendered_image_size;
4025         img.u_blo.ptr = im.rendered_image;
4026         grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
4027     }
4028     grinfo = im.grinfo;
4029     im_free(&im);
4030     return grinfo;
4033 static void
4034 rrd_set_font_desc (
4035     image_desc_t *im,int prop,char *font, double size ){
4036     if (font){
4037         strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4038         im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4039         /* if we already got one, drop it first */
4040         pango_font_description_free(im->text_prop[prop].font_desc);
4041         im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4042     };
4043     if (size > 0){
4044         im->text_prop[prop].size = size;
4045     };
4046     if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4047         pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4048     };
4051 void rrd_graph_init(
4052     image_desc_t
4053     *im)
4055     unsigned int i;
4056     char     *deffont = getenv("RRD_DEFAULT_FONT");
4057     static PangoFontMap *fontmap = NULL;
4058     PangoContext *context;
4060 #ifdef HAVE_TZSET
4061     tzset();
4062 #endif
4064     im->base = 1000;
4065     im->daemon_addr = NULL;
4066     im->draw_x_grid = 1;
4067     im->draw_y_grid = 1;
4068     im->draw_3d_border = 2;
4069     im->dynamic_labels = 0;
4070     im->extra_flags = 0;
4071     im->font_options = cairo_font_options_create();
4072     im->forceleftspace = 0;
4073     im->gdes_c = 0;
4074     im->gdes = NULL;
4075     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4076     im->grid_dash_off = 1;
4077     im->grid_dash_on = 1;
4078     im->gridfit = 1;
4079     im->grinfo = (rrd_info_t *) NULL;
4080     im->grinfo_current = (rrd_info_t *) NULL;
4081     im->imgformat = IF_PNG;
4082     im->imginfo = NULL;
4083     im->lazy = 0;
4084     im->legenddirection = TOP_DOWN;
4085     im->legendheight = 0;
4086     im->legendposition = SOUTH;
4087     im->legendwidth = 0;
4088     im->logarithmic = 0;
4089     im->maxval = DNAN;
4090     im->minval = 0;
4091     im->minval = DNAN;
4092     im->magfact = 1;
4093     im->prt_c = 0;
4094     im->rigid = 0;
4095     im->rendered_image_size = 0;
4096     im->rendered_image = NULL;
4097     im->slopemode = 0;
4098     im->step = 0;
4099     im->symbol = ' ';
4100     im->tabwidth = 40.0;
4101     im->title[0] = '\0';
4102     im->unitsexponent = 9999;
4103     im->unitslength = 6;
4104     im->viewfactor = 1.0;
4105     im->watermark[0] = '\0';
4106     im->with_markup = 0;
4107     im->ximg = 0;
4108     im->xlab_user.minsec = -1;
4109     im->xorigin = 0;
4110     im->xOriginLegend = 0;
4111     im->xOriginLegendY = 0;
4112     im->xOriginLegendY2 = 0;
4113     im->xOriginTitle = 0;
4114     im->xsize = 400;
4115     im->ygridstep = DNAN;
4116     im->yimg = 0;
4117     im->ylegend[0] = '\0';
4118     im->second_axis_scale = 0; /* 0 disables it */
4119     im->second_axis_shift = 0; /* no shift by default */
4120     im->second_axis_legend[0] = '\0';
4121     im->second_axis_format[0] = '\0';
4122     im->yorigin = 0;
4123     im->yOriginLegend = 0;
4124     im->yOriginLegendY = 0;
4125     im->yOriginLegendY2 = 0;
4126     im->yOriginTitle = 0;
4127     im->ysize = 100;
4128     im->zoom = 1;
4130     im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4131     im->cr = cairo_create(im->surface);
4133     for (i = 0; i < DIM(text_prop); i++) {
4134         im->text_prop[i].size = -1;
4135         im->text_prop[i].font_desc = NULL;
4136         rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4137     }
4139     if (fontmap == NULL){
4140         fontmap = pango_cairo_font_map_get_default();
4141     }
4143     context =  pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4145     pango_cairo_context_set_resolution(context, 100);
4147     pango_cairo_update_context(im->cr,context);
4149     im->layout = pango_layout_new(context);
4150     g_object_unref (context);
4152 //  im->layout = pango_cairo_create_layout(im->cr);
4155     cairo_font_options_set_hint_style
4156         (im->font_options, CAIRO_HINT_STYLE_FULL);
4157     cairo_font_options_set_hint_metrics
4158         (im->font_options, CAIRO_HINT_METRICS_ON);
4159     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4163     for (i = 0; i < DIM(graph_col); i++)
4164         im->graph_col[i] = graph_col[i];
4170 void rrd_graph_options(
4171     int argc,
4172     char *argv[],
4173     image_desc_t
4174     *im)
4176     int       stroff;
4177     char     *parsetime_error = NULL;
4178     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4179     time_t    start_tmp = 0, end_tmp = 0;
4180     long      long_tmp;
4181     rrd_time_value_t start_tv, end_tv;
4182     long unsigned int color;
4184     /* defines for long options without a short equivalent. should be bytes,
4185        and may not collide with (the ASCII value of) short options */
4186 #define LONGOPT_UNITS_SI 255
4188 /* *INDENT-OFF* */
4189     struct option long_options[] = {
4190         { "alt-autoscale",      no_argument,       0, 'A'},
4191         { "imgformat",          required_argument, 0, 'a'},
4192         { "font-smoothing-threshold", required_argument, 0, 'B'},
4193         { "base",               required_argument, 0, 'b'},
4194         { "color",              required_argument, 0, 'c'},
4195         { "full-size-mode",     no_argument,       0, 'D'},
4196         { "daemon",             required_argument, 0, 'd'},
4197         { "slope-mode",         no_argument,       0, 'E'},
4198         { "end",                required_argument, 0, 'e'},
4199         { "force-rules-legend", no_argument,       0, 'F'},
4200         { "imginfo",            required_argument, 0, 'f'},
4201         { "graph-render-mode",  required_argument, 0, 'G'},
4202         { "no-legend",          no_argument,       0, 'g'},
4203         { "height",             required_argument, 0, 'h'},
4204         { "no-minor",           no_argument,       0, 'I'},
4205         { "interlaced",         no_argument,       0, 'i'},
4206         { "alt-autoscale-min",  no_argument,       0, 'J'},
4207         { "only-graph",         no_argument,       0, 'j'},
4208         { "units-length",       required_argument, 0, 'L'},
4209         { "lower-limit",        required_argument, 0, 'l'},
4210         { "alt-autoscale-max",  no_argument,       0, 'M'},
4211         { "zoom",               required_argument, 0, 'm'},
4212         { "no-gridfit",         no_argument,       0, 'N'},
4213         { "font",               required_argument, 0, 'n'},
4214         { "logarithmic",        no_argument,       0, 'o'},
4215         { "pango-markup",       no_argument,       0, 'P'},
4216         { "font-render-mode",   required_argument, 0, 'R'},
4217         { "rigid",              no_argument,       0, 'r'},
4218         { "step",               required_argument, 0, 'S'},
4219         { "start",              required_argument, 0, 's'},
4220         { "tabwidth",           required_argument, 0, 'T'},
4221         { "title",              required_argument, 0, 't'},
4222         { "upper-limit",        required_argument, 0, 'u'},
4223         { "vertical-label",     required_argument, 0, 'v'},
4224         { "watermark",          required_argument, 0, 'W'},
4225         { "width",              required_argument, 0, 'w'},
4226         { "units-exponent",     required_argument, 0, 'X'},
4227         { "x-grid",             required_argument, 0, 'x'},
4228         { "alt-y-grid",         no_argument,       0, 'Y'},
4229         { "y-grid",             required_argument, 0, 'y'},
4230         { "lazy",               no_argument,       0, 'z'},
4231         { "units",              required_argument, 0, LONGOPT_UNITS_SI},
4232         { "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 */
4233         { "disable-rrdtool-tag",no_argument,       0, 1001},
4234         { "right-axis",         required_argument, 0, 1002},
4235         { "right-axis-label",   required_argument, 0, 1003},
4236         { "right-axis-format",  required_argument, 0, 1004},
4237         { "legend-position",    required_argument, 0, 1005},
4238         { "legend-direction",   required_argument, 0, 1006},
4239         { "border",             required_argument, 0, 1007},
4240         { "grid-dash",          required_argument, 0, 1008},
4241         { "dynamic-labels",     no_argument,       0, 1009},
4242         {  0, 0, 0, 0}
4243 };
4244 /* *INDENT-ON* */
4246     optind = 0;
4247     opterr = 0;         /* initialize getopt */
4248     rrd_parsetime("end-24h", &start_tv);
4249     rrd_parsetime("now", &end_tv);
4250     while (1) {
4251         int       option_index = 0;
4252         int       opt;
4253         int       col_start, col_end;
4255         opt = getopt_long(argc, argv,
4256                           "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",
4257                           long_options, &option_index);
4258         if (opt == EOF)
4259             break;
4260         switch (opt) {
4261         case 'I':
4262             im->extra_flags |= NOMINOR;
4263             break;
4264         case 'Y':
4265             im->extra_flags |= ALTYGRID;
4266             break;
4267         case 'A':
4268             im->extra_flags |= ALTAUTOSCALE;
4269             break;
4270         case 'J':
4271             im->extra_flags |= ALTAUTOSCALE_MIN;
4272             break;
4273         case 'M':
4274             im->extra_flags |= ALTAUTOSCALE_MAX;
4275             break;
4276         case 'j':
4277             im->extra_flags |= ONLY_GRAPH;
4278             break;
4279         case 'g':
4280             im->extra_flags |= NOLEGEND;
4281             break;
4282         case 1005:
4283             if (strcmp(optarg, "north") == 0) {
4284                 im->legendposition = NORTH;
4285             } else if (strcmp(optarg, "west") == 0) {
4286                 im->legendposition = WEST;
4287             } else if (strcmp(optarg, "south") == 0) {
4288                 im->legendposition = SOUTH;
4289             } else if (strcmp(optarg, "east") == 0) {
4290                 im->legendposition = EAST;
4291             } else {
4292                 rrd_set_error("unknown legend-position '%s'", optarg);
4293                 return;
4294             }
4295             break;
4296         case 1006:
4297             if (strcmp(optarg, "topdown") == 0) {
4298                 im->legenddirection = TOP_DOWN;
4299             } else if (strcmp(optarg, "bottomup") == 0) {
4300                 im->legenddirection = BOTTOM_UP;
4301             } else {
4302                 rrd_set_error("unknown legend-position '%s'", optarg);
4303                 return;
4304             }
4305             break;
4306         case 'F':
4307             im->extra_flags |= FORCE_RULES_LEGEND;
4308             break;
4309         case 1001:
4310             im->extra_flags |= NO_RRDTOOL_TAG;
4311             break;
4312         case LONGOPT_UNITS_SI:
4313             if (im->extra_flags & FORCE_UNITS) {
4314                 rrd_set_error("--units can only be used once!");
4315                 return;
4316             }
4317             if (strcmp(optarg, "si") == 0)
4318                 im->extra_flags |= FORCE_UNITS_SI;
4319             else {
4320                 rrd_set_error("invalid argument for --units: %s", optarg);
4321                 return;
4322             }
4323             break;
4324         case 'X':
4325             im->unitsexponent = atoi(optarg);
4326             break;
4327         case 'L':
4328             im->unitslength = atoi(optarg);
4329             im->forceleftspace = 1;
4330             break;
4331         case 'T':
4332             im->tabwidth = atof(optarg);
4333             break;
4334         case 'S':
4335             im->step = atoi(optarg);
4336             break;
4337         case 'N':
4338             im->gridfit = 0;
4339             break;
4340         case 'P':
4341             im->with_markup = 1;
4342             break;
4343         case 's':
4344             if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4345                 rrd_set_error("start time: %s", parsetime_error);
4346                 return;
4347             }
4348             break;
4349         case 'e':
4350             if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4351                 rrd_set_error("end time: %s", parsetime_error);
4352                 return;
4353             }
4354             break;
4355         case 'x':
4356             if (strcmp(optarg, "none") == 0) {
4357                 im->draw_x_grid = 0;
4358                 break;
4359             };
4360             if (sscanf(optarg,
4361                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4362                        scan_gtm,
4363                        &im->xlab_user.gridst,
4364                        scan_mtm,
4365                        &im->xlab_user.mgridst,
4366                        scan_ltm,
4367                        &im->xlab_user.labst,
4368                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4369                 strncpy(im->xlab_form, optarg + stroff,
4370                         sizeof(im->xlab_form) - 1);
4371                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4372                 if ((int)
4373                     (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4374                     rrd_set_error("unknown keyword %s", scan_gtm);
4375                     return;
4376                 } else if ((int)
4377                            (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4378                            == -1) {
4379                     rrd_set_error("unknown keyword %s", scan_mtm);
4380                     return;
4381                 } else if ((int)
4382                            (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4383                     rrd_set_error("unknown keyword %s", scan_ltm);
4384                     return;
4385                 }
4386                 im->xlab_user.minsec = 1;
4387                 im->xlab_user.stst = im->xlab_form;
4388             } else {
4389                 rrd_set_error("invalid x-grid format");
4390                 return;
4391             }
4392             break;
4393         case 'y':
4395             if (strcmp(optarg, "none") == 0) {
4396                 im->draw_y_grid = 0;
4397                 break;
4398             };
4399             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4400                 if (im->ygridstep <= 0) {
4401                     rrd_set_error("grid step must be > 0");
4402                     return;
4403                 } else if (im->ylabfact < 1) {
4404                     rrd_set_error("label factor must be > 0");
4405                     return;
4406                 }
4407             } else {
4408                 rrd_set_error("invalid y-grid format");
4409                 return;
4410             }
4411             break;
4412         case 1007:
4413             im->draw_3d_border = atoi(optarg);
4414             break;
4415         case 1008: /* grid-dash */
4416             if(sscanf(optarg,
4417                       "%lf:%lf",
4418                       &im->grid_dash_on,
4419                       &im->grid_dash_off) != 2) {
4420                 rrd_set_error("expected grid-dash format float:float");
4421                 return;
4422             }
4423             break;   
4424         case 1009: /* enable dynamic labels */
4425             im->dynamic_labels = 1;
4426             break;         
4427         case 1002: /* right y axis */
4429             if(sscanf(optarg,
4430                       "%lf:%lf",
4431                       &im->second_axis_scale,
4432                       &im->second_axis_shift) == 2) {
4433                 if(im->second_axis_scale==0){
4434                     rrd_set_error("the second_axis_scale  must not be 0");
4435                     return;
4436                 }
4437             } else {
4438                 rrd_set_error("invalid right-axis format expected scale:shift");
4439                 return;
4440             }
4441             break;
4442         case 1003:
4443             strncpy(im->second_axis_legend,optarg,150);
4444             im->second_axis_legend[150]='\0';
4445             break;
4446         case 1004:
4447             if (bad_format(optarg)){
4448                 rrd_set_error("use either %le or %lf formats");
4449                 return;
4450             }
4451             strncpy(im->second_axis_format,optarg,150);
4452             im->second_axis_format[150]='\0';
4453             break;
4454         case 'v':
4455             strncpy(im->ylegend, optarg, 150);
4456             im->ylegend[150] = '\0';
4457             break;
4458         case 'u':
4459             im->maxval = atof(optarg);
4460             break;
4461         case 'l':
4462             im->minval = atof(optarg);
4463             break;
4464         case 'b':
4465             im->base = atol(optarg);
4466             if (im->base != 1024 && im->base != 1000) {
4467                 rrd_set_error
4468                     ("the only sensible value for base apart from 1000 is 1024");
4469                 return;
4470             }
4471             break;
4472         case 'w':
4473             long_tmp = atol(optarg);
4474             if (long_tmp < 10) {
4475                 rrd_set_error("width below 10 pixels");
4476                 return;
4477             }
4478             im->xsize = long_tmp;
4479             break;
4480         case 'h':
4481             long_tmp = atol(optarg);
4482             if (long_tmp < 10) {
4483                 rrd_set_error("height below 10 pixels");
4484                 return;
4485             }
4486             im->ysize = long_tmp;
4487             break;
4488         case 'D':
4489             im->extra_flags |= FULL_SIZE_MODE;
4490             break;
4491         case 'i':
4492             /* interlaced png not supported at the moment */
4493             break;
4494         case 'r':
4495             im->rigid = 1;
4496             break;
4497         case 'f':
4498             im->imginfo = optarg;
4499             break;
4500         case 'a':
4501             if ((int)
4502                 (im->imgformat = if_conv(optarg)) == -1) {
4503                 rrd_set_error("unsupported graphics format '%s'", optarg);
4504                 return;
4505             }
4506             break;
4507         case 'z':
4508             im->lazy = 1;
4509             break;
4510         case 'E':
4511             im->slopemode = 1;
4512             break;
4513         case 'o':
4514             im->logarithmic = 1;
4515             break;
4516         case 'c':
4517             if (sscanf(optarg,
4518                        "%10[A-Z]#%n%8lx%n",
4519                        col_nam, &col_start, &color, &col_end) == 2) {
4520                 int       ci;
4521                 int       col_len = col_end - col_start;
4523                 switch (col_len) {
4524                 case 3:
4525                     color =
4526                         (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4527                                                          0x011000) |
4528                          ((color & 0x00F)
4529                           * 0x001100)
4530                          | 0x000000FF);
4531                     break;
4532                 case 4:
4533                     color =
4534                         (((color & 0xF000) *
4535                           0x11000) | ((color & 0x0F00) *
4536                                       0x01100) | ((color &
4537                                                    0x00F0) *
4538                                                   0x00110) |
4539                          ((color & 0x000F) * 0x00011)
4540                         );
4541                     break;
4542                 case 6:
4543                     color = (color << 8) + 0xff /* shift left by 8 */ ;
4544                     break;
4545                 case 8:
4546                     break;
4547                 default:
4548                     rrd_set_error("the color format is #RRGGBB[AA]");
4549                     return;
4550                 }
4551                 if ((ci = grc_conv(col_nam)) != -1) {
4552                     im->graph_col[ci] = gfx_hex_to_col(color);
4553                 } else {
4554                     rrd_set_error("invalid color name '%s'", col_nam);
4555                     return;
4556                 }
4557             } else {
4558                 rrd_set_error("invalid color def format");
4559                 return;
4560             }
4561             break;
4562         case 'n':{
4563             char      prop[15];
4564             double    size = 1;
4565             int       end;
4567             if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4568                 int       sindex, propidx;
4570                 if ((sindex = text_prop_conv(prop)) != -1) {
4571                     for (propidx = sindex;
4572                          propidx < TEXT_PROP_LAST; propidx++) {
4573                         if (size > 0) {
4574                             rrd_set_font_desc(im,propidx,NULL,size);
4575                         }
4576                         if ((int) strlen(optarg) > end+2) {
4577                             if (optarg[end] == ':') {
4578                                 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4579                             } else {
4580                                 rrd_set_error
4581                                     ("expected : after font size in '%s'",
4582                                      optarg);
4583                                 return;
4584                             }
4585                         }
4586                         /* only run the for loop for DEFAULT (0) for
4587                            all others, we break here. woodo programming */
4588                         if (propidx == sindex && sindex != 0)
4589                             break;
4590                     }
4591                 } else {
4592                     rrd_set_error("invalid fonttag '%s'", prop);
4593                     return;
4594                 }
4595             } else {
4596                 rrd_set_error("invalid text property format");
4597                 return;
4598             }
4599             break;
4600         }
4601         case 'm':
4602             im->zoom = atof(optarg);
4603             if (im->zoom <= 0.0) {
4604                 rrd_set_error("zoom factor must be > 0");
4605                 return;
4606             }
4607             break;
4608         case 't':
4609             strncpy(im->title, optarg, 150);
4610             im->title[150] = '\0';
4611             break;
4612         case 'R':
4613             if (strcmp(optarg, "normal") == 0) {
4614                 cairo_font_options_set_antialias
4615                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4616                 cairo_font_options_set_hint_style
4617                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4618             } else if (strcmp(optarg, "light") == 0) {
4619                 cairo_font_options_set_antialias
4620                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4621                 cairo_font_options_set_hint_style
4622                     (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4623             } else if (strcmp(optarg, "mono") == 0) {
4624                 cairo_font_options_set_antialias
4625                     (im->font_options, CAIRO_ANTIALIAS_NONE);
4626                 cairo_font_options_set_hint_style
4627                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4628             } else {
4629                 rrd_set_error("unknown font-render-mode '%s'", optarg);
4630                 return;
4631             }
4632             break;
4633         case 'G':
4634             if (strcmp(optarg, "normal") == 0)
4635                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4636             else if (strcmp(optarg, "mono") == 0)
4637                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4638             else {
4639                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4640                 return;
4641             }
4642             break;
4643         case 'B':
4644             /* not supported curently */
4645             break;
4646         case 'W':
4647             strncpy(im->watermark, optarg, 100);
4648             im->watermark[99] = '\0';
4649             break;
4650         case 'd':
4651         {
4652             if (im->daemon_addr != NULL)
4653             {
4654                 rrd_set_error ("You cannot specify --daemon "
4655                         "more than once.");
4656                 return;
4657             }
4659             im->daemon_addr = strdup(optarg);
4660             if (im->daemon_addr == NULL)
4661             {
4662               rrd_set_error("strdup failed");
4663               return;
4664             }
4666             break;
4667         }
4668         case '?':
4669             if (optopt != 0)
4670                 rrd_set_error("unknown option '%c'", optopt);
4671             else
4672                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4673             return;
4674         }
4675     } /* while (1) */
4677     {   /* try to connect to rrdcached */
4678         int status = rrdc_connect(im->daemon_addr);
4679         if (status != 0) return;
4680     }
4682     pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4683     pango_layout_context_changed(im->layout);
4687     if (im->logarithmic && im->minval <= 0) {
4688         rrd_set_error
4689             ("for a logarithmic yaxis you must specify a lower-limit > 0");
4690         return;
4691     }
4693     if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4694         /* error string is set in rrd_parsetime.c */
4695         return;
4696     }
4698     if (start_tmp < 3600 * 24 * 365 * 10) {
4699         rrd_set_error
4700             ("the first entry to fetch should be after 1980 (%ld)",
4701              start_tmp);
4702         return;
4703     }
4705     if (end_tmp < start_tmp) {
4706         rrd_set_error
4707             ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4708         return;
4709     }
4711     im->start = start_tmp;
4712     im->end = end_tmp;
4713     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4716 int rrd_graph_color(
4717     image_desc_t
4718     *im,
4719     char *var,
4720     char *err,
4721     int optional)
4723     char     *color;
4724     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4726     color = strstr(var, "#");
4727     if (color == NULL) {
4728         if (optional == 0) {
4729             rrd_set_error("Found no color in %s", err);
4730             return 0;
4731         }
4732         return 0;
4733     } else {
4734         int       n = 0;
4735         char     *rest;
4736         long unsigned int col;
4738         rest = strstr(color, ":");
4739         if (rest != NULL)
4740             n = rest - color;
4741         else
4742             n = strlen(color);
4743         switch (n) {
4744         case 7:
4745             sscanf(color, "#%6lx%n", &col, &n);
4746             col = (col << 8) + 0xff /* shift left by 8 */ ;
4747             if (n != 7)
4748                 rrd_set_error("Color problem in %s", err);
4749             break;
4750         case 9:
4751             sscanf(color, "#%8lx%n", &col, &n);
4752             if (n == 9)
4753                 break;
4754         default:
4755             rrd_set_error("Color problem in %s", err);
4756         }
4757         if (rrd_test_error())
4758             return 0;
4759         gdp->col = gfx_hex_to_col(col);
4760         return n;
4761     }
4765 int bad_format(
4766     char *fmt)
4768     char     *ptr;
4769     int       n = 0;
4771     ptr = fmt;
4772     while (*ptr != '\0')
4773         if (*ptr++ == '%') {
4775             /* line cannot end with percent char */
4776             if (*ptr == '\0')
4777                 return 1;
4778             /* '%s', '%S' and '%%' are allowed */
4779             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4780                 ptr++;
4781             /* %c is allowed (but use only with vdef!) */
4782             else if (*ptr == 'c') {
4783                 ptr++;
4784                 n = 1;
4785             }
4787             /* or else '% 6.2lf' and such are allowed */
4788             else {
4789                 /* optional padding character */
4790                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4791                     ptr++;
4792                 /* This should take care of 'm.n' with all three optional */
4793                 while (*ptr >= '0' && *ptr <= '9')
4794                     ptr++;
4795                 if (*ptr == '.')
4796                     ptr++;
4797                 while (*ptr >= '0' && *ptr <= '9')
4798                     ptr++;
4799                 /* Either 'le', 'lf' or 'lg' must follow here */
4800                 if (*ptr++ != 'l')
4801                     return 1;
4802                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4803                     ptr++;
4804                 else
4805                     return 1;
4806                 n++;
4807             }
4808         }
4810     return (n != 1);
4814 int vdef_parse(
4815     struct graph_desc_t
4816     *gdes,
4817     const char *const str)
4819     /* A VDEF currently is either "func" or "param,func"
4820      * so the parsing is rather simple.  Change if needed.
4821      */
4822     double    param;
4823     char      func[30];
4824     int       n;
4826     n = 0;
4827     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4828     if (n == (int) strlen(str)) {   /* matched */
4829         ;
4830     } else {
4831         n = 0;
4832         sscanf(str, "%29[A-Z]%n", func, &n);
4833         if (n == (int) strlen(str)) {   /* matched */
4834             param = DNAN;
4835         } else {
4836             rrd_set_error
4837                 ("Unknown function string '%s' in VDEF '%s'",
4838                  str, gdes->vname);
4839             return -1;
4840         }
4841     }
4842     if (!strcmp("PERCENT", func))
4843         gdes->vf.op = VDEF_PERCENT;
4844     else if (!strcmp("PERCENTNAN", func))
4845         gdes->vf.op = VDEF_PERCENTNAN;
4846     else if (!strcmp("MAXIMUM", func))
4847         gdes->vf.op = VDEF_MAXIMUM;
4848     else if (!strcmp("AVERAGE", func))
4849         gdes->vf.op = VDEF_AVERAGE;
4850     else if (!strcmp("STDEV", func))
4851         gdes->vf.op = VDEF_STDEV;
4852     else if (!strcmp("MINIMUM", func))
4853         gdes->vf.op = VDEF_MINIMUM;
4854     else if (!strcmp("TOTAL", func))
4855         gdes->vf.op = VDEF_TOTAL;
4856     else if (!strcmp("FIRST", func))
4857         gdes->vf.op = VDEF_FIRST;
4858     else if (!strcmp("LAST", func))
4859         gdes->vf.op = VDEF_LAST;
4860     else if (!strcmp("LSLSLOPE", func))
4861         gdes->vf.op = VDEF_LSLSLOPE;
4862     else if (!strcmp("LSLINT", func))
4863         gdes->vf.op = VDEF_LSLINT;
4864     else if (!strcmp("LSLCORREL", func))
4865         gdes->vf.op = VDEF_LSLCORREL;
4866     else {
4867         rrd_set_error
4868             ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4869         return -1;
4870     };
4871     switch (gdes->vf.op) {
4872     case VDEF_PERCENT:
4873     case VDEF_PERCENTNAN:
4874         if (isnan(param)) { /* no parameter given */
4875             rrd_set_error
4876                 ("Function '%s' needs parameter in VDEF '%s'\n",
4877                  func, gdes->vname);
4878             return -1;
4879         };
4880         if (param >= 0.0 && param <= 100.0) {
4881             gdes->vf.param = param;
4882             gdes->vf.val = DNAN;    /* undefined */
4883             gdes->vf.when = 0;  /* undefined */
4884         } else {
4885             rrd_set_error
4886                 ("Parameter '%f' out of range in VDEF '%s'\n",
4887                  param, gdes->vname);
4888             return -1;
4889         };
4890         break;
4891     case VDEF_MAXIMUM:
4892     case VDEF_AVERAGE:
4893     case VDEF_STDEV:
4894     case VDEF_MINIMUM:
4895     case VDEF_TOTAL:
4896     case VDEF_FIRST:
4897     case VDEF_LAST:
4898     case VDEF_LSLSLOPE:
4899     case VDEF_LSLINT:
4900     case VDEF_LSLCORREL:
4901         if (isnan(param)) {
4902             gdes->vf.param = DNAN;
4903             gdes->vf.val = DNAN;
4904             gdes->vf.when = 0;
4905         } else {
4906             rrd_set_error
4907                 ("Function '%s' needs no parameter in VDEF '%s'\n",
4908                  func, gdes->vname);
4909             return -1;
4910         };
4911         break;
4912     };
4913     return 0;
4917 int vdef_calc(
4918     image_desc_t *im,
4919     int gdi)
4921     graph_desc_t *src, *dst;
4922     rrd_value_t *data;
4923     long      step, steps;
4925     dst = &im->gdes[gdi];
4926     src = &im->gdes[dst->vidx];
4927     data = src->data + src->ds;
4929     steps = (src->end - src->start) / src->step;
4930 #if 0
4931     printf
4932         ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4933          src->start, src->end, steps);
4934 #endif
4935     switch (dst->vf.op) {
4936     case VDEF_PERCENT:{
4937         rrd_value_t *array;
4938         int       field;
4939         if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4940             rrd_set_error("malloc VDEV_PERCENT");
4941             return -1;
4942         }
4943         for (step = 0; step < steps; step++) {
4944             array[step] = data[step * src->ds_cnt];
4945         }
4946         qsort(array, step, sizeof(double), vdef_percent_compar);
4947         field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
4948         dst->vf.val = array[field];
4949         dst->vf.when = 0;   /* no time component */
4950         free(array);
4951 #if 0
4952         for (step = 0; step < steps; step++)
4953             printf("DEBUG: %3li:%10.2f %c\n",
4954                    step, array[step], step == field ? '*' : ' ');
4955 #endif
4956     }
4957         break;
4958     case VDEF_PERCENTNAN:{
4959         rrd_value_t *array;
4960         int       field;
4961        /* count number of "valid" values */
4962        int nancount=0;
4963        for (step = 0; step < steps; step++) {
4964          if (!isnan(data[step * src->ds_cnt])) { nancount++; }
4965        }
4966        /* and allocate it */
4967         if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
4968             rrd_set_error("malloc VDEV_PERCENT");
4969             return -1;
4970         }
4971        /* and fill it in */
4972        field=0;
4973         for (step = 0; step < steps; step++) {
4974            if (!isnan(data[step * src->ds_cnt])) {
4975                 array[field] = data[step * src->ds_cnt];
4976                field++;
4977             }
4978         }
4979         qsort(array, nancount, sizeof(double), vdef_percent_compar);
4980         field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
4981         dst->vf.val = array[field];
4982         dst->vf.when = 0;   /* no time component */
4983         free(array);
4984     }
4985         break;
4986     case VDEF_MAXIMUM:
4987         step = 0;
4988         while (step != steps && isnan(data[step * src->ds_cnt]))
4989             step++;
4990         if (step == steps) {
4991             dst->vf.val = DNAN;
4992             dst->vf.when = 0;
4993         } else {
4994             dst->vf.val = data[step * src->ds_cnt];
4995             dst->vf.when = src->start + (step + 1) * src->step;
4996         }
4997         while (step != steps) {
4998             if (finite(data[step * src->ds_cnt])) {
4999                 if (data[step * src->ds_cnt] > dst->vf.val) {
5000                     dst->vf.val = data[step * src->ds_cnt];
5001                     dst->vf.when = src->start + (step + 1) * src->step;
5002                 }
5003             }
5004             step++;
5005         }
5006         break;
5007     case VDEF_TOTAL:
5008     case VDEF_STDEV:
5009     case VDEF_AVERAGE:{
5010         int       cnt = 0;
5011         double    sum = 0.0;
5012         double    average = 0.0;
5014         for (step = 0; step < steps; step++) {
5015             if (finite(data[step * src->ds_cnt])) {
5016                 sum += data[step * src->ds_cnt];
5017                 cnt++;
5018             };
5019         }
5020         if (cnt) {
5021             if (dst->vf.op == VDEF_TOTAL) {
5022                 dst->vf.val = sum * src->step;
5023                 dst->vf.when = 0;   /* no time component */
5024             } else if (dst->vf.op == VDEF_AVERAGE) {
5025                 dst->vf.val = sum / cnt;
5026                 dst->vf.when = 0;   /* no time component */
5027             } else {
5028                 average = sum / cnt;
5029                 sum = 0.0;
5030                 for (step = 0; step < steps; step++) {
5031                     if (finite(data[step * src->ds_cnt])) {
5032                         sum += pow((data[step * src->ds_cnt] - average), 2.0);
5033                     };
5034                 }
5035                 dst->vf.val = pow(sum / cnt, 0.5);
5036                 dst->vf.when = 0;   /* no time component */
5037             };
5038         } else {
5039             dst->vf.val = DNAN;
5040             dst->vf.when = 0;
5041         }
5042     }
5043         break;
5044     case VDEF_MINIMUM:
5045         step = 0;
5046         while (step != steps && isnan(data[step * src->ds_cnt]))
5047             step++;
5048         if (step == steps) {
5049             dst->vf.val = DNAN;
5050             dst->vf.when = 0;
5051         } else {
5052             dst->vf.val = data[step * src->ds_cnt];
5053             dst->vf.when = src->start + (step + 1) * src->step;
5054         }
5055         while (step != steps) {
5056             if (finite(data[step * src->ds_cnt])) {
5057                 if (data[step * src->ds_cnt] < dst->vf.val) {
5058                     dst->vf.val = data[step * src->ds_cnt];
5059                     dst->vf.when = src->start + (step + 1) * src->step;
5060                 }
5061             }
5062             step++;
5063         }
5064         break;
5065     case VDEF_FIRST:
5066         /* The time value returned here is one step before the
5067          * actual time value.  This is the start of the first
5068          * non-NaN interval.
5069          */
5070         step = 0;
5071         while (step != steps && isnan(data[step * src->ds_cnt]))
5072             step++;
5073         if (step == steps) {    /* all entries were NaN */
5074             dst->vf.val = DNAN;
5075             dst->vf.when = 0;
5076         } else {
5077             dst->vf.val = data[step * src->ds_cnt];
5078             dst->vf.when = src->start + step * src->step;
5079         }
5080         break;
5081     case VDEF_LAST:
5082         /* The time value returned here is the
5083          * actual time value.  This is the end of the last
5084          * non-NaN interval.
5085          */
5086         step = steps - 1;
5087         while (step >= 0 && isnan(data[step * src->ds_cnt]))
5088             step--;
5089         if (step < 0) { /* all entries were NaN */
5090             dst->vf.val = DNAN;
5091             dst->vf.when = 0;
5092         } else {
5093             dst->vf.val = data[step * src->ds_cnt];
5094             dst->vf.when = src->start + (step + 1) * src->step;
5095         }
5096         break;
5097     case VDEF_LSLSLOPE:
5098     case VDEF_LSLINT:
5099     case VDEF_LSLCORREL:{
5100         /* Bestfit line by linear least squares method */
5102         int       cnt = 0;
5103         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5105         SUMx = 0;
5106         SUMy = 0;
5107         SUMxy = 0;
5108         SUMxx = 0;
5109         SUMyy = 0;
5110         for (step = 0; step < steps; step++) {
5111             if (finite(data[step * src->ds_cnt])) {
5112                 cnt++;
5113                 SUMx += step;
5114                 SUMxx += step * step;
5115                 SUMxy += step * data[step * src->ds_cnt];
5116                 SUMy += data[step * src->ds_cnt];
5117                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5118             };
5119         }
5121         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5122         y_intercept = (SUMy - slope * SUMx) / cnt;
5123         correl =
5124             (SUMxy -
5125              (SUMx * SUMy) / cnt) /
5126             sqrt((SUMxx -
5127                   (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5128         if (cnt) {
5129             if (dst->vf.op == VDEF_LSLSLOPE) {
5130                 dst->vf.val = slope;
5131                 dst->vf.when = 0;
5132             } else if (dst->vf.op == VDEF_LSLINT) {
5133                 dst->vf.val = y_intercept;
5134                 dst->vf.when = 0;
5135             } else if (dst->vf.op == VDEF_LSLCORREL) {
5136                 dst->vf.val = correl;
5137                 dst->vf.when = 0;
5138             };
5139         } else {
5140             dst->vf.val = DNAN;
5141             dst->vf.when = 0;
5142         }
5143     }
5144         break;
5145     }
5146     return 0;
5149 /* NaN < -INF < finite_values < INF */
5150 int vdef_percent_compar(
5151     const void
5152     *a,
5153     const void
5154     *b)
5156     /* Equality is not returned; this doesn't hurt except
5157      * (maybe) for a little performance.
5158      */
5160     /* First catch NaN values. They are smallest */
5161     if (isnan(*(double *) a))
5162         return -1;
5163     if (isnan(*(double *) b))
5164         return 1;
5165     /* NaN doesn't reach this part so INF and -INF are extremes.
5166      * The sign from isinf() is compatible with the sign we return
5167      */
5168     if (isinf(*(double *) a))
5169         return isinf(*(double *) a);
5170     if (isinf(*(double *) b))
5171         return isinf(*(double *) b);
5172     /* If we reach this, both values must be finite */
5173     if (*(double *) a < *(double *) b)
5174         return -1;
5175     else
5176         return 1;
5179 void grinfo_push(
5180     image_desc_t *im,
5181     char *key,
5182     rrd_info_type_t type,
5183     rrd_infoval_t value)
5185     im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5186     if (im->grinfo == NULL) {
5187         im->grinfo = im->grinfo_current;
5188     }