Code

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