Code

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