Code

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