Code

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