Code

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