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