It seems that function graph_size_location() doesn't call xtr(im, 0) if (im->extra_fl...
[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         xtr(im, 0);
2947         ytr(im, DNAN);
2948         return 0;
2949     }
2950
2951     if(im->watermark[0] != '\0') {
2952         Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2953     }
2954
2955     // calculate the width of the left vertical legend
2956     if (im->ylegend[0] != '\0') {
2957         Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2958     }
2959
2960     // calculate the width of the right vertical legend
2961     if (im->second_axis_legend[0] != '\0') {
2962         Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2963     }
2964     else{
2965         Xvertical2 = Xspacing;
2966     }
2967
2968     if (im->title[0] != '\0') {
2969         /* The title is placed "inbetween" two text lines so it
2970          ** automatically has some vertical spacing.  The horizontal
2971          ** spacing is added here, on each side.
2972          */
2973         /* if necessary, reduce the font size of the title until it fits the image width */
2974         Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2975     }
2976     else{
2977         // we have no title; get a little clearing from the top
2978         Ytitle = Yspacing;
2979     }
2980
2981     if (elements) {
2982         if (im->draw_x_grid) {
2983             // calculate the height of the horizontal labelling
2984             Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2985         }
2986         if (im->draw_y_grid || im->forceleftspace) {
2987             // calculate the width of the vertical labelling
2988             Xylabel =
2989                 gfx_get_text_width(im, 0,
2990                                    im->text_prop[TEXT_PROP_AXIS].font_desc,
2991                                    im->tabwidth, "0") * im->unitslength;
2992         }
2993     }
2994
2995     // add some space to the labelling
2996     Xylabel += Xspacing;
2997
2998     /* If the legend is printed besides the graph the width has to be
2999      ** calculated first. Placing the legend north or south of the
3000      ** graph requires the width calculation first, so the legend is
3001      ** skipped for the moment.
3002      */
3003     im->legendheight = 0;
3004     im->legendwidth = 0;
3005     if (!(im->extra_flags & NOLEGEND)) {
3006         if(im->legendposition == WEST || im->legendposition == EAST){
3007             if (leg_place(im, 1) == -1){
3008                 return -1;
3009             }
3010         }
3011     }
3012
3013     if (im->extra_flags & FULL_SIZE_MODE) {
3014
3015         /* The actual size of the image to draw has been determined by the user.
3016          ** The graph area is the space remaining after accounting for the legend,
3017          ** the watermark, the axis labels, and the title.
3018          */
3019         im->ximg = im->xsize;
3020         im->yimg = im->ysize;
3021         Xmain = im->ximg;
3022         Ymain = im->yimg;
3023
3024         /* Now calculate the total size.  Insert some spacing where
3025            desired.  im->xorigin and im->yorigin need to correspond
3026            with the lower left corner of the main graph area or, if
3027            this one is not set, the imaginary box surrounding the
3028            pie chart area. */
3029         /* Initial size calculation for the main graph area */
3030
3031         Xmain -= Xylabel;// + Xspacing;
3032         if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3033             Xmain -= im->legendwidth;// + Xspacing;
3034         }
3035         if (im->second_axis_scale != 0){
3036             Xmain -= Xylabel;
3037         }
3038         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3039             Xmain -= Xspacing;
3040         }
3041
3042         Xmain -= Xvertical + Xvertical2;
3043
3044         /* limit the remaining space to 0 */
3045         if(Xmain < 1){
3046             Xmain = 1;
3047         }
3048         im->xsize = Xmain;
3049
3050         /* Putting the legend north or south, the height can now be calculated */
3051         if (!(im->extra_flags & NOLEGEND)) {
3052             if(im->legendposition == NORTH || im->legendposition == SOUTH){
3053                 im->legendwidth = im->ximg;
3054                 if (leg_place(im, 0) == -1){
3055                     return -1;
3056                 }
3057             }
3058         }
3059
3060         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3061             Ymain -=  Yxlabel + im->legendheight;
3062         }
3063         else{
3064             Ymain -= Yxlabel;
3065         }
3066
3067         /* reserve space for the title *or* some padding above the graph */
3068         Ymain -= Ytitle;
3069
3070             /* reserve space for padding below the graph */
3071         if (im->extra_flags & NOLEGEND) {
3072             Ymain -= 0.5*Yspacing;
3073         }
3074
3075         if (im->watermark[0] != '\0') {
3076             Ymain -= Ywatermark;
3077         }
3078         /* limit the remaining height to 0 */
3079         if(Ymain < 1){
3080             Ymain = 1;
3081         }
3082         im->ysize = Ymain;
3083     } else {            /* dimension options -width and -height refer to the dimensions of the main graph area */
3084
3085         /* The actual size of the image to draw is determined from
3086          ** several sources.  The size given on the command line is
3087          ** the graph area but we need more as we have to draw labels
3088          ** and other things outside the graph area.
3089          */
3090
3091         if (elements) {
3092             Xmain = im->xsize; // + Xspacing;
3093             Ymain = im->ysize;
3094         }
3095
3096         im->ximg = Xmain + Xylabel;
3097         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3098             im->ximg += Xspacing;
3099         }
3100
3101         if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3102             im->ximg += im->legendwidth;// + Xspacing;
3103         }
3104         if (im->second_axis_scale != 0){
3105             im->ximg += Xylabel;
3106         }
3107
3108         im->ximg += Xvertical + Xvertical2;
3109
3110         if (!(im->extra_flags & NOLEGEND)) {
3111             if(im->legendposition == NORTH || im->legendposition == SOUTH){
3112                 im->legendwidth = im->ximg;
3113                 if (leg_place(im, 0) == -1){
3114                     return -1;
3115                 }
3116             }
3117         }
3118
3119         im->yimg = Ymain + Yxlabel;
3120         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3121              im->yimg += im->legendheight;
3122         }
3123
3124         /* reserve space for the title *or* some padding above the graph */
3125         if (Ytitle) {
3126             im->yimg += Ytitle;
3127         } else {
3128             im->yimg += 1.5 * Yspacing;
3129         }
3130         /* reserve space for padding below the graph */
3131         if (im->extra_flags & NOLEGEND) {
3132             im->yimg += 0.5*Yspacing;
3133         }
3134
3135         if (im->watermark[0] != '\0') {
3136             im->yimg += Ywatermark;
3137         }
3138     }
3139
3140
3141     /* In case of putting the legend in west or east position the first
3142      ** legend calculation might lead to wrong positions if some items
3143      ** are not aligned on the left hand side (e.g. centered) as the
3144      ** legendwidth wight have been increased after the item was placed.
3145      ** In this case the positions have to be recalculated.
3146      */
3147     if (!(im->extra_flags & NOLEGEND)) {
3148         if(im->legendposition == WEST || im->legendposition == EAST){
3149             if (leg_place(im, 0) == -1){
3150                 return -1;
3151             }
3152         }
3153     }
3154
3155     /* After calculating all dimensions
3156      ** it is now possible to calculate
3157      ** all offsets.
3158      */
3159     switch(im->legendposition){
3160         case NORTH:
3161             im->xOriginTitle   = (im->ximg / 2);
3162             im->yOriginTitle   = 0;
3163
3164             im->xOriginLegend  = 0;
3165             im->yOriginLegend  = Ytitle;
3166
3167             im->xOriginLegendY = 0;
3168             im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3169
3170             im->xorigin        = Xvertical + Xylabel;
3171             im->yorigin        = Ytitle + im->legendheight + Ymain;
3172
3173             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3174             if (im->second_axis_scale != 0){
3175                 im->xOriginLegendY2 += Xylabel;
3176             }
3177             im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3178
3179             break;
3180
3181         case WEST:
3182             im->xOriginTitle   = im->legendwidth + im->xsize / 2;
3183             im->yOriginTitle   = 0;
3184
3185             im->xOriginLegend  = 0;
3186             im->yOriginLegend  = Ytitle;
3187
3188             im->xOriginLegendY = im->legendwidth;
3189             im->yOriginLegendY = Ytitle + (Ymain / 2);
3190
3191             im->xorigin        = im->legendwidth + Xvertical + Xylabel;
3192             im->yorigin        = Ytitle + Ymain;
3193
3194             im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3195             if (im->second_axis_scale != 0){
3196                 im->xOriginLegendY2 += Xylabel;
3197             }
3198             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3199
3200             break;
3201
3202         case SOUTH:
3203             im->xOriginTitle   = im->ximg / 2;
3204             im->yOriginTitle   = 0;
3205
3206             im->xOriginLegend  = 0;
3207             im->yOriginLegend  = Ytitle + Ymain + Yxlabel;
3208
3209             im->xOriginLegendY = 0;
3210             im->yOriginLegendY = Ytitle + (Ymain / 2);
3211
3212             im->xorigin        = Xvertical + Xylabel;
3213             im->yorigin        = Ytitle + Ymain;
3214
3215             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3216             if (im->second_axis_scale != 0){
3217                 im->xOriginLegendY2 += Xylabel;
3218             }
3219             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3220
3221             break;
3222
3223         case EAST:
3224             im->xOriginTitle   = im->xsize / 2;
3225             im->yOriginTitle   = 0;
3226
3227             im->xOriginLegend  = Xvertical + Xylabel + Xmain + Xvertical2;
3228             if (im->second_axis_scale != 0){
3229                 im->xOriginLegend += Xylabel;
3230             }
3231             im->yOriginLegend  = Ytitle;
3232
3233             im->xOriginLegendY = 0;
3234             im->yOriginLegendY = Ytitle + (Ymain / 2);
3235
3236             im->xorigin        = Xvertical + Xylabel;
3237             im->yorigin        = Ytitle + Ymain;
3238
3239             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3240             if (im->second_axis_scale != 0){
3241                 im->xOriginLegendY2 += Xylabel;
3242             }
3243             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3244
3245             if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3246                 im->xOriginTitle    += Xspacing;
3247                 im->xOriginLegend   += Xspacing;
3248                 im->xOriginLegendY  += Xspacing;
3249                 im->xorigin         += Xspacing;
3250                 im->xOriginLegendY2 += Xspacing;
3251             }
3252             break;
3253     }
3254
3255     xtr(im, 0);
3256     ytr(im, DNAN);
3257     return 0;
3258 }
3259
3260 static cairo_status_t cairo_output(
3261     void *closure,
3262     const unsigned char
3263     *data,
3264     unsigned int length)
3265 {
3266     image_desc_t *im = (image_desc_t*)closure;
3267
3268     im->rendered_image =
3269         (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3270     if (im->rendered_image == NULL)
3271         return CAIRO_STATUS_WRITE_ERROR;
3272     memcpy(im->rendered_image + im->rendered_image_size, data, length);
3273     im->rendered_image_size += length;
3274     return CAIRO_STATUS_SUCCESS;
3275 }
3276
3277 /* draw that picture thing ... */
3278 int graph_paint(
3279     image_desc_t *im)
3280 {
3281     int       i, ii;
3282     int       lazy = lazy_check(im);
3283     double    areazero = 0.0;
3284     graph_desc_t *lastgdes = NULL;
3285     rrd_infoval_t info;
3286
3287 //    PangoFontMap *font_map = pango_cairo_font_map_get_default();
3288
3289     /* pull the data from the rrd files ... */
3290     if (data_fetch(im) == -1)
3291         return -1;
3292     /* evaluate VDEF and CDEF operations ... */
3293     if (data_calc(im) == -1)
3294         return -1;
3295     /* calculate and PRINT and GPRINT definitions. We have to do it at
3296      * this point because it will affect the length of the legends
3297      * if there are no graph elements (i==0) we stop here ...
3298      * if we are lazy, try to quit ...
3299      */
3300     i = print_calc(im);
3301     if (i < 0)
3302         return -1;
3303
3304     /* if we want and can be lazy ... quit now */
3305     if (i == 0)
3306         return 0;
3307
3308 /**************************************************************
3309  *** Calculating sizes and locations became a bit confusing ***
3310  *** so I moved this into a separate function.              ***
3311  **************************************************************/
3312     if (graph_size_location(im, i) == -1)
3313         return -1;
3314
3315     info.u_cnt = im->xorigin;
3316     grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3317     info.u_cnt = im->yorigin - im->ysize;
3318     grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3319     info.u_cnt = im->xsize;
3320     grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3321     info.u_cnt = im->ysize;
3322     grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3323     info.u_cnt = im->ximg;
3324     grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3325     info.u_cnt = im->yimg;
3326     grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3327     info.u_cnt = im->start;
3328     grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3329     info.u_cnt = im->end;
3330     grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3331
3332     /* if we want and can be lazy ... quit now */
3333     if (lazy)
3334         return 0;
3335
3336     /* get actual drawing data and find min and max values */
3337     if (data_proc(im) == -1)
3338         return -1;
3339     if (!im->logarithmic) {
3340         si_unit(im);
3341     }
3342
3343     /* identify si magnitude Kilo, Mega Giga ? */
3344     if (!im->rigid && !im->logarithmic)
3345         expand_range(im);   /* make sure the upper and lower limit are
3346                                sensible values */
3347
3348     info.u_val = im->minval;
3349     grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3350     info.u_val = im->maxval;
3351     grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3352
3353
3354     if (!calc_horizontal_grid(im))
3355         return -1;
3356     /* reset precalc */
3357     ytr(im, DNAN);
3358 /*   if (im->gridfit)
3359      apply_gridfit(im); */
3360     /* the actual graph is created by going through the individual
3361        graph elements and then drawing them */
3362     cairo_surface_destroy(im->surface);
3363     switch (im->imgformat) {
3364     case IF_PNG:
3365         im->surface =
3366             cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3367                                        im->ximg * im->zoom,
3368                                        im->yimg * im->zoom);
3369         break;
3370     case IF_PDF:
3371         im->gridfit = 0;
3372         im->surface = strlen(im->graphfile)
3373             ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3374                                        im->yimg * im->zoom)
3375             : cairo_pdf_surface_create_for_stream
3376             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3377         break;
3378     case IF_EPS:
3379         im->gridfit = 0;
3380         im->surface = strlen(im->graphfile)
3381             ?
3382             cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3383                                     im->yimg * im->zoom)
3384             : cairo_ps_surface_create_for_stream
3385             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3386         break;
3387     case IF_SVG:
3388         im->gridfit = 0;
3389         im->surface = strlen(im->graphfile)
3390             ?
3391             cairo_svg_surface_create(im->
3392                                      graphfile,
3393                                      im->ximg * im->zoom, im->yimg * im->zoom)
3394             : cairo_svg_surface_create_for_stream
3395             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3396         cairo_svg_surface_restrict_to_version
3397             (im->surface, CAIRO_SVG_VERSION_1_1);
3398         break;
3399     };
3400     cairo_destroy(im->cr);
3401     im->cr = cairo_create(im->surface);
3402     cairo_set_antialias(im->cr, im->graph_antialias);
3403     cairo_scale(im->cr, im->zoom, im->zoom);
3404 //    pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3405     gfx_new_area(im, 0, 0, 0, im->yimg,
3406                  im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3407     gfx_add_point(im, im->ximg, 0);
3408     gfx_close_path(im);
3409     gfx_new_area(im, im->xorigin,
3410                  im->yorigin,
3411                  im->xorigin +
3412                  im->xsize, im->yorigin,
3413                  im->xorigin +
3414                  im->xsize,
3415                  im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3416     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3417     gfx_close_path(im);
3418     cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3419                     im->xsize, im->ysize + 2.0);
3420     cairo_clip(im->cr);
3421     if (im->minval > 0.0)
3422         areazero = im->minval;
3423     if (im->maxval < 0.0)
3424         areazero = im->maxval;
3425     for (i = 0; i < im->gdes_c; i++) {
3426         switch (im->gdes[i].gf) {
3427         case GF_CDEF:
3428         case GF_VDEF:
3429         case GF_DEF:
3430         case GF_PRINT:
3431         case GF_GPRINT:
3432         case GF_COMMENT:
3433         case GF_TEXTALIGN:
3434         case GF_HRULE:
3435         case GF_VRULE:
3436         case GF_XPORT:
3437         case GF_SHIFT:
3438             break;
3439         case GF_TICK:
3440             for (ii = 0; ii < im->xsize; ii++) {
3441                 if (!isnan(im->gdes[i].p_data[ii])
3442                     && im->gdes[i].p_data[ii] != 0.0) {
3443                     if (im->gdes[i].yrule > 0) {
3444                         gfx_line(im,
3445                                  im->xorigin + ii,
3446                                  im->yorigin + 1.0,
3447                                  im->xorigin + ii,
3448                                  im->yorigin -
3449                                  im->gdes[i].yrule *
3450                                  im->ysize, 1.0, im->gdes[i].col);
3451                     } else if (im->gdes[i].yrule < 0) {
3452                         gfx_line(im,
3453                                  im->xorigin + ii,
3454                                  im->yorigin - im->ysize - 1.0,
3455                                  im->xorigin + ii,
3456                                  im->yorigin - im->ysize -
3457                                                 im->gdes[i].
3458                                                 yrule *
3459                                  im->ysize, 1.0, im->gdes[i].col);
3460                     }
3461                 }
3462             }
3463             break;
3464         case GF_LINE:
3465         case GF_AREA:
3466         case GF_GRAD: {
3467             rrd_value_t diffval = im->maxval - im->minval;
3468             rrd_value_t maxlimit = im->maxval + 9 * diffval;
3469             rrd_value_t minlimit = im->minval - 9 * diffval;        
3470             for (ii = 0; ii < im->xsize; ii++) {
3471                 /* fix data points at oo and -oo */
3472                 if (isinf(im->gdes[i].p_data[ii])) {
3473                     if (im->gdes[i].p_data[ii] > 0) {
3474                         im->gdes[i].p_data[ii] = im->maxval;
3475                     } else {
3476                         im->gdes[i].p_data[ii] = im->minval;
3477                     }
3478                 }
3479                 /* some versions of cairo go unstable when trying
3480                    to draw way out of the canvas ... lets not even try */
3481                if (im->gdes[i].p_data[ii] > maxlimit) {
3482                    im->gdes[i].p_data[ii] = maxlimit;
3483                }
3484                if (im->gdes[i].p_data[ii] < minlimit) {
3485                    im->gdes[i].p_data[ii] = minlimit;
3486                }
3487             }           /* for */
3488
3489             /* *******************************************************
3490                a           ___. (a,t)
3491                |   |    ___
3492                ____|   |   |   |
3493                |       |___|
3494                -------|--t-1--t--------------------------------
3495
3496                if we know the value at time t was a then
3497                we draw a square from t-1 to t with the value a.
3498
3499                ********************************************************* */
3500             if (im->gdes[i].col.alpha != 0.0) {
3501                 /* GF_LINE and friend */
3502                 if (im->gdes[i].gf == GF_LINE) {
3503                     double    last_y = 0.0;
3504                     int       draw_on = 0;
3505
3506                     cairo_save(im->cr);
3507                     cairo_new_path(im->cr);
3508                     cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3509                     if (im->gdes[i].dash) {
3510                         cairo_set_dash(im->cr,
3511                                        im->gdes[i].p_dashes,
3512                                        im->gdes[i].ndash, im->gdes[i].offset);
3513                     }
3514
3515                     for (ii = 1; ii < im->xsize; ii++) {
3516                         if (isnan(im->gdes[i].p_data[ii])
3517                             || (im->slopemode == 1
3518                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3519                             draw_on = 0;
3520                             continue;
3521                         }
3522                         if (draw_on == 0) {
3523                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3524                             if (im->slopemode == 0) {
3525                                 double    x = ii - 1 + im->xorigin;
3526                                 double    y = last_y;
3527
3528                                 gfx_line_fit(im, &x, &y);
3529                                 cairo_move_to(im->cr, x, y);
3530                                 x = ii + im->xorigin;
3531                                 y = last_y;
3532                                 gfx_line_fit(im, &x, &y);
3533                                 cairo_line_to(im->cr, x, y);
3534                             } else {
3535                                 double    x = ii - 1 + im->xorigin;
3536                                 double    y =
3537                                     ytr(im, im->gdes[i].p_data[ii - 1]);
3538                                 gfx_line_fit(im, &x, &y);
3539                                 cairo_move_to(im->cr, x, y);
3540                                 x = ii + im->xorigin;
3541                                 y = last_y;
3542                                 gfx_line_fit(im, &x, &y);
3543                                 cairo_line_to(im->cr, x, y);
3544                             }
3545                             draw_on = 1;
3546                         } else {
3547                             double    x1 = ii + im->xorigin;
3548                             double    y1 = ytr(im, im->gdes[i].p_data[ii]);
3549
3550                             if (im->slopemode == 0
3551                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3552                                 double    x = ii - 1 + im->xorigin;
3553                                 double    y = y1;
3554
3555                                 gfx_line_fit(im, &x, &y);
3556                                 cairo_line_to(im->cr, x, y);
3557                             };
3558                             last_y = y1;
3559                             gfx_line_fit(im, &x1, &y1);
3560                             cairo_line_to(im->cr, x1, y1);
3561                         };
3562                     }
3563                     cairo_set_source_rgba(im->cr,
3564                                           im->gdes[i].
3565                                           col.red,
3566                                           im->gdes[i].
3567                                           col.green,
3568                                           im->gdes[i].
3569                                           col.blue, im->gdes[i].col.alpha);
3570                     cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3571                     cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3572                     cairo_stroke(im->cr);
3573                     cairo_restore(im->cr);
3574                 } else {
3575                                         double lastx=0;
3576                                         double lasty=0;
3577                     int       idxI = -1;
3578                     double   *foreY =
3579                         (double *) malloc(sizeof(double) * im->xsize * 2);
3580                     double   *foreX =
3581                         (double *) malloc(sizeof(double) * im->xsize * 2);
3582                     double   *backY =
3583                         (double *) malloc(sizeof(double) * im->xsize * 2);
3584                     double   *backX =
3585                         (double *) malloc(sizeof(double) * im->xsize * 2);
3586                     int       drawem = 0;
3587
3588                     for (ii = 0; ii <= im->xsize; ii++) {
3589                         double    ybase, ytop;
3590
3591                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3592                             int       cntI = 1;
3593                             int       lastI = 0;
3594
3595                             while (cntI < idxI
3596                                    &&
3597                                    AlmostEqual2sComplement(foreY
3598                                                            [lastI],
3599                                                            foreY[cntI], 4)
3600                                    &&
3601                                    AlmostEqual2sComplement(foreY
3602                                                            [lastI],
3603                                                            foreY
3604                                                            [cntI + 1], 4)) {
3605                                 cntI++;
3606                             }
3607                                                         if (im->gdes[i].gf != GF_GRAD) {
3608                                 gfx_new_area(im,
3609                                              backX[0], backY[0],
3610                                              foreX[0], foreY[0],
3611                                              foreX[cntI],
3612                                              foreY[cntI], im->gdes[i].col);
3613                                                         } else {
3614                                                                 lastx = foreX[cntI];
3615                                                                 lasty = foreY[cntI];
3616                                                         }
3617                                                         while (cntI < idxI) {
3618                                 lastI = cntI;
3619                                 cntI++;
3620                                 while (cntI < idxI
3621                                        &&
3622                                        AlmostEqual2sComplement(foreY
3623                                                                [lastI],
3624                                                                foreY[cntI], 4)
3625                                        &&
3626                                        AlmostEqual2sComplement(foreY
3627                                                                [lastI],
3628                                                                foreY
3629                                                                [cntI
3630                                                                 + 1], 4)) {
3631                                     cntI++;
3632                                 }
3633                                                                 if (im->gdes[i].gf != GF_GRAD) {
3634                                         gfx_add_point(im, foreX[cntI], foreY[cntI]);
3635                                                                 } else {
3636                                                                         gfx_add_rect_fadey(im, 
3637                                                                                 lastx, foreY[0],
3638                                                                                 foreX[cntI], foreY[cntI], lasty, 
3639                                                                                 im->gdes[i].col,
3640                                                                                 im->gdes[i].col2,
3641                                                                                 im->gdes[i].gradheight
3642                                                                                 );
3643                                                                         lastx = foreX[cntI];
3644                                                                         lasty = foreY[cntI];
3645                                                                 }
3646                             }
3647                                                         if (im->gdes[i].gf != GF_GRAD) {
3648                                 gfx_add_point(im, backX[idxI], backY[idxI]);
3649                                                         } else {
3650                                                                 gfx_add_rect_fadey(im,
3651                                                                         lastx, foreY[0],
3652                                                                         backX[idxI], backY[idxI], lasty,
3653                                                                         im->gdes[i].col,
3654                                                                         im->gdes[i].col2,
3655                                                                         im->gdes[i].gradheight);
3656                                                                 lastx = backX[idxI];
3657                                                                 lasty = backY[idxI];
3658                                                         }
3659                             while (idxI > 1) {
3660                                 lastI = idxI;
3661                                 idxI--;
3662                                 while (idxI > 1
3663                                        &&
3664                                        AlmostEqual2sComplement(backY
3665                                                                [lastI],
3666                                                                backY[idxI], 4)
3667                                        &&
3668                                        AlmostEqual2sComplement(backY
3669                                                                [lastI],
3670                                                                backY
3671                                                                [idxI
3672                                                                 - 1], 4)) {
3673                                     idxI--;
3674                                 }
3675                                                                 if (im->gdes[i].gf != GF_GRAD) {
3676                                         gfx_add_point(im, backX[idxI], backY[idxI]);
3677                                                                 } else {
3678                                                                         gfx_add_rect_fadey(im,
3679                                                                                 lastx, foreY[0],
3680                                                                                 backX[idxI], backY[idxI], lasty,
3681                                                                                 im->gdes[i].col,
3682                                                                                 im->gdes[i].col2,
3683                                                                                 im->gdes[i].gradheight);
3684                                                                         lastx = backX[idxI];
3685                                                                         lasty = backY[idxI];
3686                                                                 }
3687                             }
3688                             idxI = -1;
3689                             drawem = 0;
3690                                                         if (im->gdes[i].gf != GF_GRAD) 
3691                                     gfx_close_path(im);
3692                         }
3693                         if (drawem != 0) {
3694                             drawem = 0;
3695                             idxI = -1;
3696                         }
3697                         if (ii == im->xsize)
3698                             break;
3699                         if (im->slopemode == 0 && ii == 0) {
3700                             continue;
3701                         }
3702                         if (isnan(im->gdes[i].p_data[ii])) {
3703                             drawem = 1;
3704                             continue;
3705                         }
3706                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3707                         if (lastgdes && im->gdes[i].stack) {
3708                             ybase = ytr(im, lastgdes->p_data[ii]);
3709                         } else {
3710                             ybase = ytr(im, areazero);
3711                         }
3712                         if (ybase == ytop) {
3713                             drawem = 1;
3714                             continue;
3715                         }
3716
3717                         if (ybase > ytop) {
3718                             double    extra = ytop;
3719
3720                             ytop = ybase;
3721                             ybase = extra;
3722                         }
3723                         if (im->slopemode == 0) {
3724                             backY[++idxI] = ybase - 0.2;
3725                             backX[idxI] = ii + im->xorigin - 1;
3726                             foreY[idxI] = ytop + 0.2;
3727                             foreX[idxI] = ii + im->xorigin - 1;
3728                         }
3729                         backY[++idxI] = ybase - 0.2;
3730                         backX[idxI] = ii + im->xorigin;
3731                         foreY[idxI] = ytop + 0.2;
3732                         foreX[idxI] = ii + im->xorigin;
3733                     }
3734                     /* close up any remaining area */
3735                     free(foreY);
3736                     free(foreX);
3737                     free(backY);
3738                     free(backX);
3739                 }       /* else GF_LINE */
3740             }
3741             /* if color != 0x0 */
3742             /* make sure we do not run into trouble when stacking on NaN */
3743             for (ii = 0; ii < im->xsize; ii++) {
3744                 if (isnan(im->gdes[i].p_data[ii])) {
3745                     if (lastgdes && (im->gdes[i].stack)) {
3746                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3747                     } else {
3748                         im->gdes[i].p_data[ii] = areazero;
3749                     }
3750                 }
3751             }
3752             lastgdes = &(im->gdes[i]);
3753             break;
3754         } /* GF_AREA, GF_LINE, GF_GRAD */
3755         case GF_STACK:
3756             rrd_set_error
3757                 ("STACK should already be turned into LINE or AREA here");
3758             return -1;
3759             break;
3760         }               /* switch */
3761     }
3762     cairo_reset_clip(im->cr);
3763
3764     /* grid_paint also does the text */
3765     if (!(im->extra_flags & ONLY_GRAPH))
3766         grid_paint(im);
3767     if (!(im->extra_flags & ONLY_GRAPH))
3768         axis_paint(im);
3769     /* the RULES are the last thing to paint ... */
3770     for (i = 0; i < im->gdes_c; i++) {
3771
3772         switch (im->gdes[i].gf) {
3773         case GF_HRULE:
3774             if (im->gdes[i].yrule >= im->minval
3775                 && im->gdes[i].yrule <= im->maxval) {
3776                 cairo_save(im->cr);
3777                 if (im->gdes[i].dash) {
3778                     cairo_set_dash(im->cr,
3779                                    im->gdes[i].p_dashes,
3780                                    im->gdes[i].ndash, im->gdes[i].offset);
3781                 }
3782                 gfx_line(im, im->xorigin,
3783                          ytr(im, im->gdes[i].yrule),
3784                          im->xorigin + im->xsize,
3785                          ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3786                 cairo_stroke(im->cr);
3787                 cairo_restore(im->cr);
3788             }
3789             break;
3790         case GF_VRULE:
3791             if (im->gdes[i].xrule >= im->start
3792                 && im->gdes[i].xrule <= im->end) {
3793                 cairo_save(im->cr);
3794                 if (im->gdes[i].dash) {
3795                     cairo_set_dash(im->cr,
3796                                    im->gdes[i].p_dashes,
3797                                    im->gdes[i].ndash, im->gdes[i].offset);
3798                 }
3799                 gfx_line(im,
3800                          xtr(im, im->gdes[i].xrule),
3801                          im->yorigin, xtr(im,
3802                                           im->
3803                                           gdes[i].
3804                                           xrule),
3805                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3806                 cairo_stroke(im->cr);
3807                 cairo_restore(im->cr);
3808             }
3809             break;
3810         default:
3811             break;
3812         }
3813     }
3814
3815
3816     switch (im->imgformat) {
3817     case IF_PNG:
3818     {
3819         cairo_status_t status;
3820
3821         status = strlen(im->graphfile) ?
3822             cairo_surface_write_to_png(im->surface, im->graphfile)
3823             : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3824                                                 im);
3825
3826         if (status != CAIRO_STATUS_SUCCESS) {
3827             rrd_set_error("Could not save png to '%s'", im->graphfile);
3828             return 1;
3829         }
3830         break;
3831     }
3832     default:
3833         if (strlen(im->graphfile)) {
3834             cairo_show_page(im->cr);
3835         } else {
3836             cairo_surface_finish(im->surface);
3837         }
3838         break;
3839     }
3840
3841     return 0;
3842 }
3843
3844
3845 /*****************************************************
3846  * graph stuff
3847  *****************************************************/
3848
3849 int gdes_alloc(
3850     image_desc_t *im)
3851 {
3852
3853     im->gdes_c++;
3854     if ((im->gdes = (graph_desc_t *)
3855          rrd_realloc(im->gdes, (im->gdes_c)
3856                      * sizeof(graph_desc_t))) == NULL) {
3857         rrd_set_error("realloc graph_descs");
3858         return -1;
3859     }
3860
3861
3862     im->gdes[im->gdes_c - 1].step = im->step;
3863     im->gdes[im->gdes_c - 1].step_orig = im->step;
3864     im->gdes[im->gdes_c - 1].stack = 0;
3865     im->gdes[im->gdes_c - 1].linewidth = 0;
3866     im->gdes[im->gdes_c - 1].debug = 0;
3867     im->gdes[im->gdes_c - 1].start = im->start;
3868     im->gdes[im->gdes_c - 1].start_orig = im->start;
3869     im->gdes[im->gdes_c - 1].end = im->end;
3870     im->gdes[im->gdes_c - 1].end_orig = im->end;
3871     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3872     im->gdes[im->gdes_c - 1].data = NULL;
3873     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3874     im->gdes[im->gdes_c - 1].data_first = 0;
3875     im->gdes[im->gdes_c - 1].p_data = NULL;
3876     im->gdes[im->gdes_c - 1].rpnp = NULL;
3877     im->gdes[im->gdes_c - 1].p_dashes = NULL;
3878     im->gdes[im->gdes_c - 1].shift = 0.0;
3879     im->gdes[im->gdes_c - 1].dash = 0;
3880     im->gdes[im->gdes_c - 1].ndash = 0;
3881     im->gdes[im->gdes_c - 1].offset = 0;
3882     im->gdes[im->gdes_c - 1].col.red = 0.0;
3883     im->gdes[im->gdes_c - 1].col.green = 0.0;
3884     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3885     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3886     im->gdes[im->gdes_c - 1].col2.red = 0.0;
3887     im->gdes[im->gdes_c - 1].col2.green = 0.0;
3888     im->gdes[im->gdes_c - 1].col2.blue = 0.0;
3889     im->gdes[im->gdes_c - 1].col2.alpha = 0.0;
3890     im->gdes[im->gdes_c - 1].gradheight = 50.0;
3891     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3892     im->gdes[im->gdes_c - 1].format[0] = '\0';
3893     im->gdes[im->gdes_c - 1].strftm = 0;
3894     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3895     im->gdes[im->gdes_c - 1].ds = -1;
3896     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3897     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3898     im->gdes[im->gdes_c - 1].yrule = DNAN;
3899     im->gdes[im->gdes_c - 1].xrule = 0;
3900     im->gdes[im->gdes_c - 1].daemon[0] = 0;
3901     return 0;
3902 }
3903
3904 /* copies input untill the first unescaped colon is found
3905    or until input ends. backslashes have to be escaped as well */
3906 int scan_for_col(
3907     const char *const input,
3908     int len,
3909     char *const output)
3910 {
3911     int       inp, outp = 0;
3912
3913     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3914         if (input[inp] == '\\'
3915             && input[inp + 1] != '\0'
3916             && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3917             output[outp++] = input[++inp];
3918         } else {
3919             output[outp++] = input[inp];
3920         }
3921     }
3922     output[outp] = '\0';
3923     return inp;
3924 }
3925
3926 /* Now just a wrapper around rrd_graph_v */
3927 int rrd_graph(
3928     int argc,
3929     char **argv,
3930     char ***prdata,
3931     int *xsize,
3932     int *ysize,
3933     FILE * stream,
3934     double *ymin,
3935     double *ymax)
3936 {
3937     int       prlines = 0;
3938     rrd_info_t *grinfo = NULL;
3939     rrd_info_t *walker;
3940
3941     grinfo = rrd_graph_v(argc, argv);
3942     if (grinfo == NULL)
3943         return -1;
3944     walker = grinfo;
3945     (*prdata) = NULL;
3946     while (walker) {
3947         if (strcmp(walker->key, "image_info") == 0) {
3948             prlines++;
3949             if (((*prdata) =
3950                  (char**)rrd_realloc((*prdata),
3951                              (prlines + 1) * sizeof(char *))) == NULL) {
3952                 rrd_set_error("realloc prdata");
3953                 return 0;
3954             }
3955             /* imginfo goes to position 0 in the prdata array */
3956             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3957                                              + 2) * sizeof(char));
3958             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3959             (*prdata)[prlines] = NULL;
3960         }
3961         /* skip anything else */
3962         walker = walker->next;
3963     }
3964     walker = grinfo;
3965     *xsize = 0;
3966     *ysize = 0;
3967     *ymin = 0;
3968     *ymax = 0;
3969     while (walker) {
3970         if (strcmp(walker->key, "image_width") == 0) {
3971             *xsize = walker->value.u_cnt;
3972         } else if (strcmp(walker->key, "image_height") == 0) {
3973             *ysize = walker->value.u_cnt;
3974         } else if (strcmp(walker->key, "value_min") == 0) {
3975             *ymin = walker->value.u_val;
3976         } else if (strcmp(walker->key, "value_max") == 0) {
3977             *ymax = walker->value.u_val;
3978         } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3979             prlines++;
3980             if (((*prdata) =
3981                  (char**)rrd_realloc((*prdata),
3982                              (prlines + 1) * sizeof(char *))) == NULL) {
3983                 rrd_set_error("realloc prdata");
3984                 return 0;
3985             }
3986             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3987                                              + 2) * sizeof(char));
3988             (*prdata)[prlines] = NULL;
3989             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3990         } else if (strcmp(walker->key, "image") == 0) {
3991             if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3992                    (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3993                 rrd_set_error("writing image");
3994                 return 0;
3995             }
3996         }
3997         /* skip anything else */
3998         walker = walker->next;
3999     }
4000     rrd_info_free(grinfo);
4001     return 0;
4002 }
4003
4004
4005 /* Some surgery done on this function, it became ridiculously big.
4006 ** Things moved:
4007 ** - initializing     now in rrd_graph_init()
4008 ** - options parsing  now in rrd_graph_options()
4009 ** - script parsing   now in rrd_graph_script()
4010 */
4011 rrd_info_t *rrd_graph_v(
4012     int argc,
4013     char **argv)
4014 {
4015     image_desc_t im;
4016     rrd_info_t *grinfo;
4017     char *old_locale;
4018     rrd_graph_init(&im);
4019     /* a dummy surface so that we can measure text sizes for placements */
4020     old_locale = setlocale(LC_NUMERIC, NULL);
4021     setlocale(LC_NUMERIC, "C");
4022     rrd_graph_options(argc, argv, &im);
4023     if (rrd_test_error()) {
4024         setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4025         rrd_info_free(im.grinfo);
4026         im_free(&im);
4027         return NULL;
4028     }
4029
4030     if (optind >= argc) {
4031         setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4032         rrd_info_free(im.grinfo);
4033         im_free(&im);
4034         rrd_set_error("missing filename");
4035         return NULL;
4036     }
4037
4038     if (strlen(argv[optind]) >= MAXPATH) {
4039         setlocale(LC_NUMERIC, old_locale); /* reenable locale */
4040         rrd_set_error("filename (including path) too long");
4041         rrd_info_free(im.grinfo);
4042         im_free(&im);
4043         return NULL;
4044     }
4045
4046     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
4047     im.graphfile[MAXPATH - 1] = '\0';
4048
4049     if (strcmp(im.graphfile, "-") == 0) {
4050         im.graphfile[0] = '\0';
4051     }
4052
4053     rrd_graph_script(argc, argv, &im, 1);
4054     setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
4055
4056     if (rrd_test_error()) {
4057         rrd_info_free(im.grinfo);
4058         im_free(&im);
4059         return NULL;
4060     }
4061
4062     /* Everything is now read and the actual work can start */
4063
4064     if (graph_paint(&im) == -1) {
4065         rrd_info_free(im.grinfo);
4066         im_free(&im);
4067         return NULL;
4068     }
4069
4070
4071     /* The image is generated and needs to be output.
4072      ** Also, if needed, print a line with information about the image.
4073      */
4074
4075     if (im.imginfo) {
4076         rrd_infoval_t info;
4077         char     *path;
4078         char     *filename;
4079
4080         path = strdup(im.graphfile);
4081         filename = basename(path);
4082         info.u_str =
4083             sprintf_alloc(im.imginfo,
4084                           filename,
4085                           (long) (im.zoom *
4086                                   im.ximg), (long) (im.zoom * im.yimg));
4087         grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
4088         free(info.u_str);
4089         free(path);
4090     }
4091     if (im.rendered_image) {
4092         rrd_infoval_t img;
4093
4094         img.u_blo.size = im.rendered_image_size;
4095         img.u_blo.ptr = im.rendered_image;
4096         grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
4097     }
4098     grinfo = im.grinfo;
4099     im_free(&im);
4100     return grinfo;
4101 }
4102
4103 static void
4104 rrd_set_font_desc (
4105     image_desc_t *im,int prop,char *font, double size ){
4106     if (font){
4107         strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4108         im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4109         /* if we already got one, drop it first */
4110         pango_font_description_free(im->text_prop[prop].font_desc);
4111         im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4112     };
4113     if (size > 0){
4114         im->text_prop[prop].size = size;
4115     };
4116     if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4117         pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4118     };
4119 }
4120
4121 void rrd_graph_init(
4122     image_desc_t
4123     *im)
4124 {
4125     unsigned int i;
4126     char     *deffont = getenv("RRD_DEFAULT_FONT");
4127     static PangoFontMap *fontmap = NULL;
4128     PangoContext *context;
4129
4130 #ifdef HAVE_TZSET
4131     tzset();
4132 #endif
4133
4134     im->base = 1000;
4135     im->daemon_addr = NULL;
4136     im->draw_x_grid = 1;
4137     im->draw_y_grid = 1;
4138     im->draw_3d_border = 2;
4139     im->dynamic_labels = 0;
4140     im->extra_flags = 0;
4141     im->font_options = cairo_font_options_create();
4142     im->forceleftspace = 0;
4143     im->gdes_c = 0;
4144     im->gdes = NULL;
4145     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4146     im->grid_dash_off = 1;
4147     im->grid_dash_on = 1;
4148     im->gridfit = 1;
4149     im->grinfo = (rrd_info_t *) NULL;
4150     im->grinfo_current = (rrd_info_t *) NULL;
4151     im->imgformat = IF_PNG;
4152     im->imginfo = NULL;
4153     im->lazy = 0;
4154     im->legenddirection = TOP_DOWN;
4155     im->legendheight = 0;
4156     im->legendposition = SOUTH;
4157     im->legendwidth = 0;
4158     im->logarithmic = 0;
4159     im->maxval = DNAN;
4160     im->minval = 0;
4161     im->minval = DNAN;
4162     im->magfact = 1;
4163     im->prt_c = 0;
4164     im->rigid = 0;
4165     im->rendered_image_size = 0;
4166     im->rendered_image = NULL;
4167     im->slopemode = 0;
4168     im->step = 0;
4169     im->symbol = ' ';
4170     im->tabwidth = 40.0;
4171     im->title[0] = '\0';
4172     im->unitsexponent = 9999;
4173     im->unitslength = 6;
4174     im->viewfactor = 1.0;
4175     im->watermark[0] = '\0';
4176     im->with_markup = 0;
4177     im->ximg = 0;
4178     im->xlab_user.minsec = -1;
4179     im->xorigin = 0;
4180     im->xOriginLegend = 0;
4181     im->xOriginLegendY = 0;
4182     im->xOriginLegendY2 = 0;
4183     im->xOriginTitle = 0;
4184     im->xsize = 400;
4185     im->ygridstep = DNAN;
4186     im->yimg = 0;
4187     im->ylegend[0] = '\0';
4188     im->second_axis_scale = 0; /* 0 disables it */
4189     im->second_axis_shift = 0; /* no shift by default */
4190     im->second_axis_legend[0] = '\0';
4191     im->second_axis_format[0] = '\0';
4192     im->yorigin = 0;
4193     im->yOriginLegend = 0;
4194     im->yOriginLegendY = 0;
4195     im->yOriginLegendY2 = 0;
4196     im->yOriginTitle = 0;
4197     im->ysize = 100;
4198     im->zoom = 1;
4199
4200     im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4201     im->cr = cairo_create(im->surface);
4202
4203     for (i = 0; i < DIM(text_prop); i++) {
4204         im->text_prop[i].size = -1;
4205         im->text_prop[i].font_desc = NULL;
4206         rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4207     }
4208
4209     if (fontmap == NULL){
4210         fontmap = pango_cairo_font_map_get_default();
4211     }
4212
4213     context =  pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4214
4215     pango_cairo_context_set_resolution(context, 100);
4216
4217     pango_cairo_update_context(im->cr,context);
4218
4219     im->layout = pango_layout_new(context);
4220     g_object_unref (context);
4221
4222 //  im->layout = pango_cairo_create_layout(im->cr);
4223
4224
4225     cairo_font_options_set_hint_style
4226         (im->font_options, CAIRO_HINT_STYLE_FULL);
4227     cairo_font_options_set_hint_metrics
4228         (im->font_options, CAIRO_HINT_METRICS_ON);
4229     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4230
4231
4232
4233     for (i = 0; i < DIM(graph_col); i++)
4234         im->graph_col[i] = graph_col[i];
4235
4236
4237 }
4238
4239
4240 void rrd_graph_options(
4241     int argc,
4242     char *argv[],
4243     image_desc_t
4244     *im)
4245 {
4246     int       stroff;
4247     char     *parsetime_error = NULL;
4248     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4249     time_t    start_tmp = 0, end_tmp = 0;
4250     long      long_tmp;
4251     rrd_time_value_t start_tv, end_tv;
4252     long unsigned int color;
4253
4254     /* defines for long options without a short equivalent. should be bytes,
4255        and may not collide with (the ASCII value of) short options */
4256 #define LONGOPT_UNITS_SI 255
4257
4258 /* *INDENT-OFF* */
4259     struct option long_options[] = {
4260         { "alt-autoscale",      no_argument,       0, 'A'},
4261         { "imgformat",          required_argument, 0, 'a'},
4262         { "font-smoothing-threshold", required_argument, 0, 'B'},
4263         { "base",               required_argument, 0, 'b'},
4264         { "color",              required_argument, 0, 'c'},
4265         { "full-size-mode",     no_argument,       0, 'D'},
4266         { "daemon",             required_argument, 0, 'd'},
4267         { "slope-mode",         no_argument,       0, 'E'},
4268         { "end",                required_argument, 0, 'e'},
4269         { "force-rules-legend", no_argument,       0, 'F'},
4270         { "imginfo",            required_argument, 0, 'f'},
4271         { "graph-render-mode",  required_argument, 0, 'G'},
4272         { "no-legend",          no_argument,       0, 'g'},
4273         { "height",             required_argument, 0, 'h'},
4274         { "no-minor",           no_argument,       0, 'I'},
4275         { "interlaced",         no_argument,       0, 'i'},
4276         { "alt-autoscale-min",  no_argument,       0, 'J'},
4277         { "only-graph",         no_argument,       0, 'j'},
4278         { "units-length",       required_argument, 0, 'L'},
4279         { "lower-limit",        required_argument, 0, 'l'},
4280         { "alt-autoscale-max",  no_argument,       0, 'M'},
4281         { "zoom",               required_argument, 0, 'm'},
4282         { "no-gridfit",         no_argument,       0, 'N'},
4283         { "font",               required_argument, 0, 'n'},
4284         { "logarithmic",        no_argument,       0, 'o'},
4285         { "pango-markup",       no_argument,       0, 'P'},
4286         { "font-render-mode",   required_argument, 0, 'R'},
4287         { "rigid",              no_argument,       0, 'r'},
4288         { "step",               required_argument, 0, 'S'},
4289         { "start",              required_argument, 0, 's'},
4290         { "tabwidth",           required_argument, 0, 'T'},
4291         { "title",              required_argument, 0, 't'},
4292         { "upper-limit",        required_argument, 0, 'u'},
4293         { "vertical-label",     required_argument, 0, 'v'},
4294         { "watermark",          required_argument, 0, 'W'},
4295         { "width",              required_argument, 0, 'w'},
4296         { "units-exponent",     required_argument, 0, 'X'},
4297         { "x-grid",             required_argument, 0, 'x'},
4298         { "alt-y-grid",         no_argument,       0, 'Y'},
4299         { "y-grid",             required_argument, 0, 'y'},
4300         { "lazy",               no_argument,       0, 'z'},
4301         { "units",              required_argument, 0, LONGOPT_UNITS_SI},
4302         { "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 */
4303         { "disable-rrdtool-tag",no_argument,       0, 1001},
4304         { "right-axis",         required_argument, 0, 1002},
4305         { "right-axis-label",   required_argument, 0, 1003},
4306         { "right-axis-format",  required_argument, 0, 1004},
4307         { "legend-position",    required_argument, 0, 1005},
4308         { "legend-direction",   required_argument, 0, 1006},
4309         { "border",             required_argument, 0, 1007},
4310         { "grid-dash",          required_argument, 0, 1008},
4311         { "dynamic-labels",     no_argument,       0, 1009},
4312         { "week-fmt",           required_argument, 0, 1010},
4313         {  0, 0, 0, 0}
4314 };
4315 /* *INDENT-ON* */
4316
4317     optind = 0;
4318     opterr = 0;         /* initialize getopt */
4319     rrd_parsetime("end-24h", &start_tv);
4320     rrd_parsetime("now", &end_tv);
4321     while (1) {
4322         int       option_index = 0;
4323         int       opt;
4324         int       col_start, col_end;
4325
4326         opt = getopt_long(argc, argv,
4327                           "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",
4328                           long_options, &option_index);
4329         if (opt == EOF)
4330             break;
4331         switch (opt) {
4332         case 'I':
4333             im->extra_flags |= NOMINOR;
4334             break;
4335         case 'Y':
4336             im->extra_flags |= ALTYGRID;
4337             break;
4338         case 'A':
4339             im->extra_flags |= ALTAUTOSCALE;
4340             break;
4341         case 'J':
4342             im->extra_flags |= ALTAUTOSCALE_MIN;
4343             break;
4344         case 'M':
4345             im->extra_flags |= ALTAUTOSCALE_MAX;
4346             break;
4347         case 'j':
4348             im->extra_flags |= ONLY_GRAPH;
4349             break;
4350         case 'g':
4351             im->extra_flags |= NOLEGEND;
4352             break;
4353         case 1005:
4354             if (strcmp(optarg, "north") == 0) {
4355                 im->legendposition = NORTH;
4356             } else if (strcmp(optarg, "west") == 0) {
4357                 im->legendposition = WEST;
4358             } else if (strcmp(optarg, "south") == 0) {
4359                 im->legendposition = SOUTH;
4360             } else if (strcmp(optarg, "east") == 0) {
4361                 im->legendposition = EAST;
4362             } else {
4363                 rrd_set_error("unknown legend-position '%s'", optarg);
4364                 return;
4365             }
4366             break;
4367         case 1006:
4368             if (strcmp(optarg, "topdown") == 0) {
4369                 im->legenddirection = TOP_DOWN;
4370             } else if (strcmp(optarg, "bottomup") == 0) {
4371                 im->legenddirection = BOTTOM_UP;
4372             } else {
4373                 rrd_set_error("unknown legend-position '%s'", optarg);
4374                 return;
4375             }
4376             break;
4377         case 'F':
4378             im->extra_flags |= FORCE_RULES_LEGEND;
4379             break;
4380         case 1001:
4381             im->extra_flags |= NO_RRDTOOL_TAG;
4382             break;
4383         case LONGOPT_UNITS_SI:
4384             if (im->extra_flags & FORCE_UNITS) {
4385                 rrd_set_error("--units can only be used once!");
4386                 return;
4387             }
4388             if (strcmp(optarg, "si") == 0)
4389                 im->extra_flags |= FORCE_UNITS_SI;
4390             else {
4391                 rrd_set_error("invalid argument for --units: %s", optarg);
4392                 return;
4393             }
4394             break;
4395         case 'X':
4396             im->unitsexponent = atoi(optarg);
4397             break;
4398         case 'L':
4399             im->unitslength = atoi(optarg);
4400             im->forceleftspace = 1;
4401             break;
4402         case 'T':
4403             im->tabwidth = atof(optarg);
4404             break;
4405         case 'S':
4406             im->step = atoi(optarg);
4407             break;
4408         case 'N':
4409             im->gridfit = 0;
4410             break;
4411         case 'P':
4412             im->with_markup = 1;
4413             break;
4414         case 's':
4415             if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4416                 rrd_set_error("start time: %s", parsetime_error);
4417                 return;
4418             }
4419             break;
4420         case 'e':
4421             if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4422                 rrd_set_error("end time: %s", parsetime_error);
4423                 return;
4424             }
4425             break;
4426         case 'x':
4427             if (strcmp(optarg, "none") == 0) {
4428                 im->draw_x_grid = 0;
4429                 break;
4430             };
4431             if (sscanf(optarg,
4432                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4433                        scan_gtm,
4434                        &im->xlab_user.gridst,
4435                        scan_mtm,
4436                        &im->xlab_user.mgridst,
4437                        scan_ltm,
4438                        &im->xlab_user.labst,
4439                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4440                 strncpy(im->xlab_form, optarg + stroff,
4441                         sizeof(im->xlab_form) - 1);
4442                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4443                 if ((int)
4444                     (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4445                     rrd_set_error("unknown keyword %s", scan_gtm);
4446                     return;
4447                 } else if ((int)
4448                            (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4449                            == -1) {
4450                     rrd_set_error("unknown keyword %s", scan_mtm);
4451                     return;
4452                 } else if ((int)
4453                            (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4454                     rrd_set_error("unknown keyword %s", scan_ltm);
4455                     return;
4456                 }
4457                 im->xlab_user.minsec = 1;
4458                 im->xlab_user.stst = im->xlab_form;
4459             } else {
4460                 rrd_set_error("invalid x-grid format");
4461                 return;
4462             }
4463             break;
4464         case 'y':
4465
4466             if (strcmp(optarg, "none") == 0) {
4467                 im->draw_y_grid = 0;
4468                 break;
4469             };
4470             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4471                 if (im->ygridstep <= 0) {
4472                     rrd_set_error("grid step must be > 0");
4473                     return;
4474                 } else if (im->ylabfact < 1) {
4475                     rrd_set_error("label factor must be > 0");
4476                     return;
4477                 }
4478             } else {
4479                 rrd_set_error("invalid y-grid format");
4480                 return;
4481             }
4482             break;
4483         case 1007:
4484             im->draw_3d_border = atoi(optarg);
4485             break;
4486         case 1008: /* grid-dash */
4487             if(sscanf(optarg,
4488                       "%lf:%lf",
4489                       &im->grid_dash_on,
4490                       &im->grid_dash_off) != 2) {
4491                 rrd_set_error("expected grid-dash format float:float");
4492                 return;
4493             }
4494             break;   
4495         case 1009: /* enable dynamic labels */
4496             im->dynamic_labels = 1;
4497             break;         
4498         case 1010:
4499             strncpy(week_fmt,optarg,sizeof week_fmt);
4500             week_fmt[(sizeof week_fmt)-1]='\0';
4501             break;
4502         case 1002: /* right y axis */
4503
4504             if(sscanf(optarg,
4505                       "%lf:%lf",
4506                       &im->second_axis_scale,
4507                       &im->second_axis_shift) == 2) {
4508                 if(im->second_axis_scale==0){
4509                     rrd_set_error("the second_axis_scale  must not be 0");
4510                     return;
4511                 }
4512             } else {
4513                 rrd_set_error("invalid right-axis format expected scale:shift");
4514                 return;
4515             }
4516             break;
4517         case 1003:
4518             strncpy(im->second_axis_legend,optarg,150);
4519             im->second_axis_legend[150]='\0';
4520             break;
4521         case 1004:
4522             if (bad_format(optarg)){
4523                 rrd_set_error("use either %le or %lf formats");
4524                 return;
4525             }
4526             strncpy(im->second_axis_format,optarg,150);
4527             im->second_axis_format[150]='\0';
4528             break;
4529         case 'v':
4530             strncpy(im->ylegend, optarg, 150);
4531             im->ylegend[150] = '\0';
4532             break;
4533         case 'u':
4534             im->maxval = atof(optarg);
4535             break;
4536         case 'l':
4537             im->minval = atof(optarg);
4538             break;
4539         case 'b':
4540             im->base = atol(optarg);
4541             if (im->base != 1024 && im->base != 1000) {
4542                 rrd_set_error
4543                     ("the only sensible value for base apart from 1000 is 1024");
4544                 return;
4545             }
4546             break;
4547         case 'w':
4548             long_tmp = atol(optarg);
4549             if (long_tmp < 10) {
4550                 rrd_set_error("width below 10 pixels");
4551                 return;
4552             }
4553             im->xsize = long_tmp;
4554             break;
4555         case 'h':
4556             long_tmp = atol(optarg);
4557             if (long_tmp < 10) {
4558                 rrd_set_error("height below 10 pixels");
4559                 return;
4560             }
4561             im->ysize = long_tmp;
4562             break;
4563         case 'D':
4564             im->extra_flags |= FULL_SIZE_MODE;
4565             break;
4566         case 'i':
4567             /* interlaced png not supported at the moment */
4568             break;
4569         case 'r':
4570             im->rigid = 1;
4571             break;
4572         case 'f':
4573             im->imginfo = optarg;
4574             break;
4575         case 'a':
4576             if ((int)
4577                 (im->imgformat = if_conv(optarg)) == -1) {
4578                 rrd_set_error("unsupported graphics format '%s'", optarg);
4579                 return;
4580             }
4581             break;
4582         case 'z':
4583             im->lazy = 1;
4584             break;
4585         case 'E':
4586             im->slopemode = 1;
4587             break;
4588         case 'o':
4589             im->logarithmic = 1;
4590             break;
4591         case 'c':
4592             if (sscanf(optarg,
4593                        "%10[A-Z]#%n%8lx%n",
4594                        col_nam, &col_start, &color, &col_end) == 2) {
4595                 int       ci;
4596                 int       col_len = col_end - col_start;
4597
4598                 switch (col_len) {
4599                 case 3:
4600                     color =
4601                         (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4602                                                          0x011000) |
4603                          ((color & 0x00F)
4604                           * 0x001100)
4605                          | 0x000000FF);
4606                     break;
4607                 case 4:
4608                     color =
4609                         (((color & 0xF000) *
4610                           0x11000) | ((color & 0x0F00) *
4611                                       0x01100) | ((color &
4612                                                    0x00F0) *
4613                                                   0x00110) |
4614                          ((color & 0x000F) * 0x00011)
4615                         );
4616                     break;
4617                 case 6:
4618                     color = (color << 8) + 0xff /* shift left by 8 */ ;
4619                     break;
4620                 case 8:
4621                     break;
4622                 default:
4623                     rrd_set_error("the color format is #RRGGBB[AA]");
4624                     return;
4625                 }
4626                 if ((ci = grc_conv(col_nam)) != -1) {
4627                     im->graph_col[ci] = gfx_hex_to_col(color);
4628                 } else {
4629                     rrd_set_error("invalid color name '%s'", col_nam);
4630                     return;
4631                 }
4632             } else {
4633                 rrd_set_error("invalid color def format");
4634                 return;
4635             }
4636             break;
4637         case 'n':{
4638             char      prop[15];
4639             double    size = 1;
4640             int       end;
4641
4642             if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4643                 int       sindex, propidx;
4644
4645                 if ((sindex = text_prop_conv(prop)) != -1) {
4646                     for (propidx = sindex;
4647                          propidx < TEXT_PROP_LAST; propidx++) {
4648                         if (size > 0) {
4649                             rrd_set_font_desc(im,propidx,NULL,size);
4650                         }
4651                         if ((int) strlen(optarg) > end+2) {
4652                             if (optarg[end] == ':') {
4653                                 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4654                             } else {
4655                                 rrd_set_error
4656                                     ("expected : after font size in '%s'",
4657                                      optarg);
4658                                 return;
4659                             }
4660                         }
4661                         /* only run the for loop for DEFAULT (0) for
4662                            all others, we break here. woodo programming */
4663                         if (propidx == sindex && sindex != 0)
4664                             break;
4665                     }
4666                 } else {
4667                     rrd_set_error("invalid fonttag '%s'", prop);
4668                     return;
4669                 }
4670             } else {
4671                 rrd_set_error("invalid text property format");
4672                 return;
4673             }
4674             break;
4675         }
4676         case 'm':
4677             im->zoom = atof(optarg);
4678             if (im->zoom <= 0.0) {
4679                 rrd_set_error("zoom factor must be > 0");
4680                 return;
4681             }
4682             break;
4683         case 't':
4684             strncpy(im->title, optarg, 150);
4685             im->title[150] = '\0';
4686             break;
4687         case 'R':
4688             if (strcmp(optarg, "normal") == 0) {
4689                 cairo_font_options_set_antialias
4690                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4691                 cairo_font_options_set_hint_style
4692                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4693             } else if (strcmp(optarg, "light") == 0) {
4694                 cairo_font_options_set_antialias
4695                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4696                 cairo_font_options_set_hint_style
4697                     (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4698             } else if (strcmp(optarg, "mono") == 0) {
4699                 cairo_font_options_set_antialias
4700                     (im->font_options, CAIRO_ANTIALIAS_NONE);
4701                 cairo_font_options_set_hint_style
4702                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4703             } else {
4704                 rrd_set_error("unknown font-render-mode '%s'", optarg);
4705                 return;
4706             }
4707             break;
4708         case 'G':
4709             if (strcmp(optarg, "normal") == 0)
4710                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4711             else if (strcmp(optarg, "mono") == 0)
4712                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4713             else {
4714                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4715                 return;
4716             }
4717             break;
4718         case 'B':
4719             /* not supported curently */
4720             break;
4721         case 'W':
4722             strncpy(im->watermark, optarg, 100);
4723             im->watermark[99] = '\0';
4724             break;
4725         case 'd':
4726         {
4727             if (im->daemon_addr != NULL)
4728             {
4729                 rrd_set_error ("You cannot specify --daemon "
4730                         "more than once.");
4731                 return;
4732             }
4733
4734             im->daemon_addr = strdup(optarg);
4735             if (im->daemon_addr == NULL)
4736             {
4737               rrd_set_error("strdup failed");
4738               return;
4739             }
4740
4741             break;
4742         }
4743         case '?':
4744             if (optopt != 0)
4745                 rrd_set_error("unknown option '%c'", optopt);
4746             else
4747                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4748             return;
4749         }
4750     } /* while (1) */
4751
4752     pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4753     pango_layout_context_changed(im->layout);
4754
4755
4756
4757     if (im->logarithmic && im->minval <= 0) {
4758         rrd_set_error
4759             ("for a logarithmic yaxis you must specify a lower-limit > 0");
4760         return;
4761     }
4762
4763     if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4764         /* error string is set in rrd_parsetime.c */
4765         return;
4766     }
4767
4768     if (start_tmp < 3600 * 24 * 365 * 10) {
4769         rrd_set_error
4770             ("the first entry to fetch should be after 1980 (%ld)",
4771              start_tmp);
4772         return;
4773     }
4774
4775     if (end_tmp < start_tmp) {
4776         rrd_set_error
4777             ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4778         return;
4779     }
4780
4781     im->start = start_tmp;
4782     im->end = end_tmp;
4783     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4784 }
4785
4786 int rrd_graph_color(
4787     image_desc_t
4788     *im,
4789     char *var,
4790     char *err,
4791     int optional)
4792 {
4793     char     *color;
4794     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4795
4796     color = strstr(var, "#");
4797     if (color == NULL) {
4798         if (optional == 0) {
4799             rrd_set_error("Found no color in %s", err);
4800             return 0;
4801         }
4802         return 0;
4803     } else {
4804         int       n = 0;
4805         char     *rest;
4806         long unsigned int col;
4807
4808         rest = strstr(color, ":");
4809         if (rest != NULL)
4810             n = rest - color;
4811         else
4812             n = strlen(color);
4813         switch (n) {
4814         case 7:
4815             sscanf(color, "#%6lx%n", &col, &n);
4816             col = (col << 8) + 0xff /* shift left by 8 */ ;
4817             if (n != 7)
4818                 rrd_set_error("Color problem in %s", err);
4819             break;
4820         case 9:
4821             sscanf(color, "#%8lx%n", &col, &n);
4822             if (n == 9)
4823                 break;
4824         default:
4825             rrd_set_error("Color problem in %s", err);
4826         }
4827         if (rrd_test_error())
4828             return 0;
4829         gdp->col = gfx_hex_to_col(col);
4830         return n;
4831     }
4832 }
4833
4834
4835 int bad_format(
4836     char *fmt)
4837 {
4838     char     *ptr;
4839     int       n = 0;
4840
4841     ptr = fmt;
4842     while (*ptr != '\0')
4843         if (*ptr++ == '%') {
4844
4845             /* line cannot end with percent char */
4846             if (*ptr == '\0')
4847                 return 1;
4848             /* '%s', '%S' and '%%' are allowed */
4849             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4850                 ptr++;
4851             /* %c is allowed (but use only with vdef!) */
4852             else if (*ptr == 'c') {
4853                 ptr++;
4854                 n = 1;
4855             }
4856
4857             /* or else '% 6.2lf' and such are allowed */
4858             else {
4859                 /* optional padding character */
4860                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4861                     ptr++;
4862                 /* This should take care of 'm.n' with all three optional */
4863                 while (*ptr >= '0' && *ptr <= '9')
4864                     ptr++;
4865                 if (*ptr == '.')
4866                     ptr++;
4867                 while (*ptr >= '0' && *ptr <= '9')
4868                     ptr++;
4869                 /* Either 'le', 'lf' or 'lg' must follow here */
4870                 if (*ptr++ != 'l')
4871                     return 1;
4872                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4873                     ptr++;
4874                 else
4875                     return 1;
4876                 n++;
4877             }
4878         }
4879
4880     return (n != 1);
4881 }
4882
4883
4884 int vdef_parse(
4885     struct graph_desc_t
4886     *gdes,
4887     const char *const str)
4888 {
4889     /* A VDEF currently is either "func" or "param,func"
4890      * so the parsing is rather simple.  Change if needed.
4891      */
4892     double    param;
4893     char      func[30];
4894     int       n;
4895
4896     n = 0;
4897     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4898     if (n == (int) strlen(str)) {   /* matched */
4899         ;
4900     } else {
4901         n = 0;
4902         sscanf(str, "%29[A-Z]%n", func, &n);
4903         if (n == (int) strlen(str)) {   /* matched */
4904             param = DNAN;
4905         } else {
4906             rrd_set_error
4907                 ("Unknown function string '%s' in VDEF '%s'",
4908                  str, gdes->vname);
4909             return -1;
4910         }
4911     }
4912     if (!strcmp("PERCENT", func))
4913         gdes->vf.op = VDEF_PERCENT;
4914     else if (!strcmp("PERCENTNAN", func))
4915         gdes->vf.op = VDEF_PERCENTNAN;
4916     else if (!strcmp("MAXIMUM", func))
4917         gdes->vf.op = VDEF_MAXIMUM;
4918     else if (!strcmp("AVERAGE", func))
4919         gdes->vf.op = VDEF_AVERAGE;
4920     else if (!strcmp("STDEV", func))
4921         gdes->vf.op = VDEF_STDEV;
4922     else if (!strcmp("MINIMUM", func))
4923         gdes->vf.op = VDEF_MINIMUM;
4924     else if (!strcmp("TOTAL", func))
4925         gdes->vf.op = VDEF_TOTAL;
4926     else if (!strcmp("FIRST", func))
4927         gdes->vf.op = VDEF_FIRST;
4928     else if (!strcmp("LAST", func))
4929         gdes->vf.op = VDEF_LAST;
4930     else if (!strcmp("LSLSLOPE", func))
4931         gdes->vf.op = VDEF_LSLSLOPE;
4932     else if (!strcmp("LSLINT", func))
4933         gdes->vf.op = VDEF_LSLINT;
4934     else if (!strcmp("LSLCORREL", func))
4935         gdes->vf.op = VDEF_LSLCORREL;
4936     else {
4937         rrd_set_error
4938             ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4939         return -1;
4940     };
4941     switch (gdes->vf.op) {
4942     case VDEF_PERCENT:
4943     case VDEF_PERCENTNAN:
4944         if (isnan(param)) { /* no parameter given */
4945             rrd_set_error
4946                 ("Function '%s' needs parameter in VDEF '%s'\n",
4947                  func, gdes->vname);
4948             return -1;
4949         };
4950         if (param >= 0.0 && param <= 100.0) {
4951             gdes->vf.param = param;
4952             gdes->vf.val = DNAN;    /* undefined */
4953             gdes->vf.when = 0;  /* undefined */
4954             gdes->vf.never = 1;
4955         } else {
4956             rrd_set_error
4957                 ("Parameter '%f' out of range in VDEF '%s'\n",
4958                  param, gdes->vname);
4959             return -1;
4960         };
4961         break;
4962     case VDEF_MAXIMUM:
4963     case VDEF_AVERAGE:
4964     case VDEF_STDEV:
4965     case VDEF_MINIMUM:
4966     case VDEF_TOTAL:
4967     case VDEF_FIRST:
4968     case VDEF_LAST:
4969     case VDEF_LSLSLOPE:
4970     case VDEF_LSLINT:
4971     case VDEF_LSLCORREL:
4972         if (isnan(param)) {
4973             gdes->vf.param = DNAN;
4974             gdes->vf.val = DNAN;
4975             gdes->vf.when = 0;
4976             gdes->vf.never = 1;
4977         } else {
4978             rrd_set_error
4979                 ("Function '%s' needs no parameter in VDEF '%s'\n",
4980                  func, gdes->vname);
4981             return -1;
4982         };
4983         break;
4984     };
4985     return 0;
4986 }
4987
4988
4989 int vdef_calc(
4990     image_desc_t *im,
4991     int gdi)
4992 {
4993     graph_desc_t *src, *dst;
4994     rrd_value_t *data;
4995     long      step, steps;
4996
4997     dst = &im->gdes[gdi];
4998     src = &im->gdes[dst->vidx];
4999     data = src->data + src->ds;
5000
5001     steps = (src->end - src->start) / src->step;
5002 #if 0
5003     printf
5004         ("DEBUG: start == %lu, end == %lu, %lu steps\n",
5005          src->start, src->end, steps);
5006 #endif
5007     switch (dst->vf.op) {
5008     case VDEF_PERCENT:{
5009         rrd_value_t *array;
5010         int       field;
5011         if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
5012             rrd_set_error("malloc VDEV_PERCENT");
5013             return -1;
5014         }
5015         for (step = 0; step < steps; step++) {
5016             array[step] = data[step * src->ds_cnt];
5017         }
5018         qsort(array, step, sizeof(double), vdef_percent_compar);
5019         field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
5020         dst->vf.val = array[field];
5021         dst->vf.when = 0;   /* no time component */
5022         dst->vf.never = 1;
5023         free(array);
5024 #if 0
5025         for (step = 0; step < steps; step++)
5026             printf("DEBUG: %3li:%10.2f %c\n",
5027                    step, array[step], step == field ? '*' : ' ');
5028 #endif
5029     }
5030         break;
5031     case VDEF_PERCENTNAN:{
5032         rrd_value_t *array;
5033         int       field;
5034        /* count number of "valid" values */
5035        int nancount=0;
5036        for (step = 0; step < steps; step++) {
5037          if (!isnan(data[step * src->ds_cnt])) { nancount++; }
5038        }
5039        /* and allocate it */
5040         if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
5041             rrd_set_error("malloc VDEV_PERCENT");
5042             return -1;
5043         }
5044        /* and fill it in */
5045        field=0;
5046         for (step = 0; step < steps; step++) {
5047            if (!isnan(data[step * src->ds_cnt])) {
5048                 array[field] = data[step * src->ds_cnt];
5049                field++;
5050             }
5051         }
5052         qsort(array, nancount, sizeof(double), vdef_percent_compar);
5053         field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
5054         dst->vf.val = array[field];
5055         dst->vf.when = 0;   /* no time component */
5056         dst->vf.never = 1;
5057         free(array);
5058     }
5059         break;
5060     case VDEF_MAXIMUM:
5061         step = 0;
5062         while (step != steps && isnan(data[step * src->ds_cnt]))
5063             step++;
5064         if (step == steps) {
5065             dst->vf.val = DNAN;
5066             dst->vf.when = 0;
5067             dst->vf.never = 1;
5068         } else {
5069             dst->vf.val = data[step * src->ds_cnt];
5070             dst->vf.when = src->start + (step + 1) * src->step;
5071             dst->vf.never = 0;
5072         }
5073         while (step != steps) {
5074             if (finite(data[step * src->ds_cnt])) {
5075                 if (data[step * src->ds_cnt] > dst->vf.val) {
5076                     dst->vf.val = data[step * src->ds_cnt];
5077                     dst->vf.when = src->start + (step + 1) * src->step;
5078                     dst->vf.never = 0;
5079                 }
5080             }
5081             step++;
5082         }
5083         break;
5084     case VDEF_TOTAL:
5085     case VDEF_STDEV:
5086     case VDEF_AVERAGE:{
5087         int       cnt = 0;
5088         double    sum = 0.0;
5089         double    average = 0.0;
5090
5091         for (step = 0; step < steps; step++) {
5092             if (finite(data[step * src->ds_cnt])) {
5093                 sum += data[step * src->ds_cnt];
5094                 cnt++;
5095             };
5096         }
5097         if (cnt) {
5098             if (dst->vf.op == VDEF_TOTAL) {
5099                 dst->vf.val = sum * src->step;
5100                 dst->vf.when = 0;   /* no time component */
5101                 dst->vf.never = 1;
5102             } else if (dst->vf.op == VDEF_AVERAGE) {
5103                 dst->vf.val = sum / cnt;
5104                 dst->vf.when = 0;   /* no time component */
5105                 dst->vf.never = 1;
5106             } else {
5107                 average = sum / cnt;
5108                 sum = 0.0;
5109                 for (step = 0; step < steps; step++) {
5110                     if (finite(data[step * src->ds_cnt])) {
5111                         sum += pow((data[step * src->ds_cnt] - average), 2.0);
5112                     };
5113                 }
5114                 dst->vf.val = pow(sum / cnt, 0.5);
5115                 dst->vf.when = 0;   /* no time component */
5116                 dst->vf.never = 1;
5117             };
5118         } else {
5119             dst->vf.val = DNAN;
5120             dst->vf.when = 0;
5121             dst->vf.never = 1;
5122         }
5123     }
5124         break;
5125     case VDEF_MINIMUM:
5126         step = 0;
5127         while (step != steps && isnan(data[step * src->ds_cnt]))
5128             step++;
5129         if (step == steps) {
5130             dst->vf.val = DNAN;
5131             dst->vf.when = 0;
5132             dst->vf.never = 1;
5133         } else {
5134             dst->vf.val = data[step * src->ds_cnt];
5135             dst->vf.when = src->start + (step + 1) * src->step;
5136             dst->vf.never = 0;
5137         }
5138         while (step != steps) {
5139             if (finite(data[step * src->ds_cnt])) {
5140                 if (data[step * src->ds_cnt] < dst->vf.val) {
5141                     dst->vf.val = data[step * src->ds_cnt];
5142                     dst->vf.when = src->start + (step + 1) * src->step;
5143                     dst->vf.never = 0;
5144                 }
5145             }
5146             step++;
5147         }
5148         break;
5149     case VDEF_FIRST:
5150         /* The time value returned here is one step before the
5151          * actual time value.  This is the start of the first
5152          * non-NaN interval.
5153          */
5154         step = 0;
5155         while (step != steps && isnan(data[step * src->ds_cnt]))
5156             step++;
5157         if (step == steps) {    /* all entries were NaN */
5158             dst->vf.val = DNAN;
5159             dst->vf.when = 0;
5160             dst->vf.never = 1;
5161         } else {
5162             dst->vf.val = data[step * src->ds_cnt];
5163             dst->vf.when = src->start + step * src->step;
5164             dst->vf.never = 0;
5165         }
5166         break;
5167     case VDEF_LAST:
5168         /* The time value returned here is the
5169          * actual time value.  This is the end of the last
5170          * non-NaN interval.
5171          */
5172         step = steps - 1;
5173         while (step >= 0 && isnan(data[step * src->ds_cnt]))
5174             step--;
5175         if (step < 0) { /* all entries were NaN */
5176             dst->vf.val = DNAN;
5177             dst->vf.when = 0;
5178             dst->vf.never = 1;
5179         } else {
5180             dst->vf.val = data[step * src->ds_cnt];
5181             dst->vf.when = src->start + (step + 1) * src->step;
5182             dst->vf.never = 0;
5183         }
5184         break;
5185     case VDEF_LSLSLOPE:
5186     case VDEF_LSLINT:
5187     case VDEF_LSLCORREL:{
5188         /* Bestfit line by linear least squares method */
5189
5190         int       cnt = 0;
5191         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5192
5193         SUMx = 0;
5194         SUMy = 0;
5195         SUMxy = 0;
5196         SUMxx = 0;
5197         SUMyy = 0;
5198         for (step = 0; step < steps; step++) {
5199             if (finite(data[step * src->ds_cnt])) {
5200                 cnt++;
5201                 SUMx += step;
5202                 SUMxx += step * step;
5203                 SUMxy += step * data[step * src->ds_cnt];
5204                 SUMy += data[step * src->ds_cnt];
5205                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5206             };
5207         }
5208
5209         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5210         y_intercept = (SUMy - slope * SUMx) / cnt;
5211         correl =
5212             (SUMxy -
5213              (SUMx * SUMy) / cnt) /
5214             sqrt((SUMxx -
5215                   (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5216         if (cnt) {
5217             if (dst->vf.op == VDEF_LSLSLOPE) {
5218                 dst->vf.val = slope;
5219                 dst->vf.when = 0;
5220                 dst->vf.never = 1;
5221             } else if (dst->vf.op == VDEF_LSLINT) {
5222                 dst->vf.val = y_intercept;
5223                 dst->vf.when = 0;
5224                 dst->vf.never = 1;
5225             } else if (dst->vf.op == VDEF_LSLCORREL) {
5226                 dst->vf.val = correl;
5227                 dst->vf.when = 0;
5228                 dst->vf.never = 1;
5229             };
5230         } else {
5231             dst->vf.val = DNAN;
5232             dst->vf.when = 0;
5233             dst->vf.never = 1;
5234         }
5235     }
5236         break;
5237     }
5238     return 0;
5239 }
5240
5241 /* NaN < -INF < finite_values < INF */
5242 int vdef_percent_compar(
5243     const void
5244     *a,
5245     const void
5246     *b)
5247 {
5248     /* Equality is not returned; this doesn't hurt except
5249      * (maybe) for a little performance.
5250      */
5251
5252     /* First catch NaN values. They are smallest */
5253     if (isnan(*(double *) a))
5254         return -1;
5255     if (isnan(*(double *) b))
5256         return 1;
5257     /* NaN doesn't reach this part so INF and -INF are extremes.
5258      * The sign from isinf() is compatible with the sign we return
5259      */
5260     if (isinf(*(double *) a))
5261         return isinf(*(double *) a);
5262     if (isinf(*(double *) b))
5263         return isinf(*(double *) b);
5264     /* If we reach this, both values must be finite */
5265     if (*(double *) a < *(double *) b)
5266         return -1;
5267     else
5268         return 1;
5269 }
5270
5271 void grinfo_push(
5272     image_desc_t *im,
5273     char *key,
5274     rrd_info_type_t type,
5275     rrd_infoval_t value)
5276 {
5277     im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5278     if (im->grinfo == NULL) {
5279         im->grinfo = im->grinfo_current;
5280     }
5281 }
5282
5283
5284 void time_clean(
5285     char *result,
5286     char *format)
5287 {
5288     int       j, jj;
5289     
5290 /*     Handling based on
5291        - ANSI C99 Specifications                         http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
5292        - Single UNIX Specification version 2             http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html 
5293        - POSIX:2001/Single UNIX Specification version 3  http://www.opengroup.org/onlinepubs/009695399/functions/strftime.html
5294        - POSIX:2008 Specifications                       http://www.opengroup.org/onlinepubs/9699919799/functions/strftime.html
5295        Specifications tells 
5296        "If a conversion specifier is not one of the above, the behavior is undefined."
5297
5298       C99 tells
5299        "A conversion specifier consists of a % character, possibly followed by an E or O modifier character (described below), followed by a character that determines the behavior of the conversion specifier.
5300
5301       POSIX:2001 tells
5302       "A conversion specification consists of a '%' character, possibly followed by an E or O modifier, and a terminating conversion specifier character that determines the conversion specification's behavior."
5303
5304       POSIX:2008 introduce more complexe behavior that are not handled here.
5305
5306       According to this, this code will replace:
5307       - % followed by @ by a %@
5308       - % followed by   by a %SPACE
5309       - % followed by . by a %.
5310       - % followed by % by a %
5311       - % followed by t by a TAB
5312       - % followed by E then anything by '-'
5313       - % followed by O then anything by '-'
5314       - % followed by anything else by at least one '-'. More characters may be added to better fit expected output length
5315 */
5316
5317     jj = 0;
5318     for(j = 0; (j < FMT_LEG_LEN - 1) && (jj < FMT_LEG_LEN); j++) { /* we don't need to parse the last char */
5319         if (format[j] == '%') {
5320             if ((format[j+1] == 'E') || (format[j+1] == 'O')) {
5321                 result[jj++] = '-';
5322                 j+=2; /* We skip next 2 following char */
5323             } else if ((format[j+1] == 'C') || (format[j+1] == 'd') ||
5324                        (format[j+1] == 'g') || (format[j+1] == 'H') ||
5325                        (format[j+1] == 'I') || (format[j+1] == 'm') ||
5326                        (format[j+1] == 'M') || (format[j+1] == 'S') ||
5327                        (format[j+1] == 'U') || (format[j+1] == 'V') ||
5328                        (format[j+1] == 'W') || (format[j+1] == 'y')) {
5329                 result[jj++] = '-';
5330                 if (jj < FMT_LEG_LEN) {
5331                     result[jj++] = '-';
5332                 }
5333                 j++; /* We skip the following char */
5334             } else if (format[j+1] == 'j') {
5335                 result[jj++] = '-';
5336                 if (jj < FMT_LEG_LEN - 1) {
5337                     result[jj++] = '-';
5338                     result[jj++] = '-';
5339                }
5340                 j++; /* We skip the following char */
5341             } else if ((format[j+1] == 'G') || (format[j+1] == 'Y')) {
5342                 /* Assuming Year on 4 digit */
5343                 result[jj++] = '-';
5344                 if (jj < FMT_LEG_LEN - 2) {
5345                     result[jj++] = '-';
5346                     result[jj++] = '-';
5347                     result[jj++] = '-';
5348                 }
5349                 j++; /* We skip the following char */
5350             } else if (format[j+1] == 'R') {
5351                 result[jj++] = '-';
5352                 if (jj < FMT_LEG_LEN - 3) {
5353                     result[jj++] = '-';
5354                     result[jj++] = ':';
5355                     result[jj++] = '-';
5356                     result[jj++] = '-';
5357                 }
5358                 j++; /* We skip the following char */
5359             } else if (format[j+1] == 'T') {
5360                 result[jj++] = '-';
5361                 if (jj < FMT_LEG_LEN - 6) {
5362                     result[jj++] = '-';
5363                     result[jj++] = ':';
5364                     result[jj++] = '-';
5365                     result[jj++] = '-';
5366                     result[jj++] = ':';
5367                     result[jj++] = '-';
5368                     result[jj++] = '-';
5369                 }
5370                 j++; /* We skip the following char */
5371             } else if (format[j+1] == 'F') {
5372                 result[jj++] = '-';
5373                 if (jj < FMT_LEG_LEN - 8) {
5374                     result[jj++] = '-';
5375                     result[jj++] = '-';
5376                     result[jj++] = '-';
5377                     result[jj++] = '-';
5378                     result[jj++] = '-';
5379                     result[jj++] = '-';
5380                     result[jj++] = '-';
5381                     result[jj++] = '-';
5382                     result[jj++] = '-';
5383                 }
5384                 j++; /* We skip the following char */
5385             } else if (format[j+1] == 'D') {
5386                 result[jj++] = '-';
5387                 if (jj < FMT_LEG_LEN - 6) {
5388                     result[jj++] = '-';
5389                     result[jj++] = '/';
5390                     result[jj++] = '-';
5391                     result[jj++] = '-';
5392                     result[jj++] = '/';
5393                     result[jj++] = '-';
5394                     result[jj++] = '-';
5395                 }
5396                 j++; /* We skip the following char */
5397             } else if (format[j+1] == 'n') {
5398                 result[jj++] = '\r';
5399                 result[jj++] = '\n';
5400                 j++; /* We skip the following char */
5401             } else if (format[j+1] == 't') {
5402                 result[jj++] = '\t';
5403                 j++; /* We skip the following char */
5404             } else if (format[j+1] == '%') {
5405                 result[jj++] = '%';
5406                 j++; /* We skip the following char */
5407             } else if (format[j+1] == ' ') {
5408                 if (jj < FMT_LEG_LEN - 1) {
5409                     result[jj++] = '%';
5410                     result[jj++] = ' ';
5411                 }
5412                 j++; /* We skip the following char */
5413             } else if (format[j+1] == '.') {
5414                 if (jj < FMT_LEG_LEN - 1) {
5415                     result[jj++] = '%';
5416                     result[jj++] = '.';
5417                 }
5418                 j++; /* We skip the following char */
5419             } else if (format[j+1] == '@') {
5420                 if (jj < FMT_LEG_LEN - 1) {
5421                     result[jj++] = '%';
5422                     result[jj++] = '@';
5423                 }
5424                 j++; /* We skip the following char */
5425             } else {
5426                 result[jj++] = '-';
5427                 j++; /* We skip the following char */
5428             }
5429         } else {
5430                 result[jj++] = format[j];
5431         }
5432     }
5433     result[jj] = '\0'; /* We must force the end of the string */
5434 }