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