Code

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