Code

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