Code

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