update to the time_clean function by Jean-Edouard Babin
[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 xlab_t    xlab[] = {
63     {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
64     ,
65     {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
66     ,
67     {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
68     ,
69     {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
70     ,
71     {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
72     ,
73     {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
74     ,
75     {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
76     ,
77     {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
78     ,
79     {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
80     ,
81     /*{300,             0,   TMT_HOUR,3,    TMT_HOUR,12,   TMT_HOUR,12,    12*3600,"%a %p"},  this looks silly */
82     {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
83     ,
84     {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
85     ,
86     {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
87     ,
88     {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
89     ,
90     {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
91     ,
92     {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
93      "Week %V"}
94     ,
95     {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
96      "%b"}
97     ,
98     {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
99      "%b"}
100     ,
101     {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
102     ,
103     {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
104      365 * 24 * 3600, "%y"}
105     ,
106     {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
107 };
108
109 /* sensible y label intervals ...*/
110
111 ylab_t    ylab[] = {
112     {0.1, {1, 2, 5, 10}
113      }
114     ,
115     {0.2, {1, 5, 10, 20}
116      }
117     ,
118     {0.5, {1, 2, 4, 10}
119      }
120     ,
121     {1.0, {1, 2, 5, 10}
122      }
123     ,
124     {2.0, {1, 5, 10, 20}
125      }
126     ,
127     {5.0, {1, 2, 4, 10}
128      }
129     ,
130     {10.0, {1, 2, 5, 10}
131      }
132     ,
133     {20.0, {1, 5, 10, 20}
134      }
135     ,
136     {50.0, {1, 2, 4, 10}
137      }
138     ,
139     {100.0, {1, 2, 5, 10}
140      }
141     ,
142     {200.0, {1, 5, 10, 20}
143      }
144     ,
145     {500.0, {1, 2, 4, 10}
146      }
147     ,
148     {0.0, {0, 0, 0, 0}
149      }
150 };
151
152
153 gfx_color_t graph_col[] =   /* default colors */
154 {
155     {1.00, 1.00, 1.00, 1.00},   /* canvas     */
156     {0.95, 0.95, 0.95, 1.00},   /* background */
157     {0.81, 0.81, 0.81, 1.00},   /* shade A    */
158     {0.62, 0.62, 0.62, 1.00},   /* shade B    */
159     {0.56, 0.56, 0.56, 0.75},   /* grid       */
160     {0.87, 0.31, 0.31, 0.60},   /* major grid */
161     {0.00, 0.00, 0.00, 1.00},   /* font       */
162     {0.50, 0.12, 0.12, 1.00},   /* arrow      */
163     {0.12, 0.12, 0.12, 1.00},   /* axis       */
164     {0.00, 0.00, 0.00, 1.00}    /* frame      */
165 };
166
167
168 /* #define DEBUG */
169
170 #ifdef DEBUG
171 # define DPRINT(x)    (void)(printf x, printf("\n"))
172 #else
173 # define DPRINT(x)
174 #endif
175
176
177 /* initialize with xtr(im,0); */
178 int xtr(
179     image_desc_t *im,
180     time_t mytime)
181 {
182     static double pixie;
183
184     if (mytime == 0) {
185         pixie = (double) im->xsize / (double) (im->end - im->start);
186         return im->xorigin;
187     }
188     return (int) ((double) im->xorigin + pixie * (mytime - im->start));
189 }
190
191 /* translate data values into y coordinates */
192 double ytr(
193     image_desc_t *im,
194     double value)
195 {
196     static double pixie;
197     double    yval;
198
199     if (isnan(value)) {
200         if (!im->logarithmic)
201             pixie = (double) im->ysize / (im->maxval - im->minval);
202         else
203             pixie =
204                 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
205         yval = im->yorigin;
206     } else if (!im->logarithmic) {
207         yval = im->yorigin - pixie * (value - im->minval);
208     } else {
209         if (value < im->minval) {
210             yval = im->yorigin;
211         } else {
212             yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
213         }
214     }
215     return yval;
216 }
217
218
219
220 /* conversion function for symbolic entry names */
221
222
223 #define conv_if(VV,VVV) \
224    if (strcmp(#VV, string) == 0) return VVV ;
225
226 enum gf_en gf_conv(
227     char *string)
228 {
229
230     conv_if(PRINT, GF_PRINT);
231     conv_if(GPRINT, GF_GPRINT);
232     conv_if(COMMENT, GF_COMMENT);
233     conv_if(HRULE, GF_HRULE);
234     conv_if(VRULE, GF_VRULE);
235     conv_if(LINE, GF_LINE);
236     conv_if(AREA, GF_AREA);
237         conv_if(GRAD, GF_GRAD);
238     conv_if(STACK, GF_STACK);
239     conv_if(TICK, GF_TICK);
240     conv_if(TEXTALIGN, GF_TEXTALIGN);
241     conv_if(DEF, GF_DEF);
242     conv_if(CDEF, GF_CDEF);
243     conv_if(VDEF, GF_VDEF);
244     conv_if(XPORT, GF_XPORT);
245     conv_if(SHIFT, GF_SHIFT);
246
247     return (enum gf_en)(-1);
248 }
249
250 enum gfx_if_en if_conv(
251     char *string)
252 {
253
254     conv_if(PNG, IF_PNG);
255     conv_if(SVG, IF_SVG);
256     conv_if(EPS, IF_EPS);
257     conv_if(PDF, IF_PDF);
258
259     return (enum gfx_if_en)(-1);
260 }
261
262 enum tmt_en tmt_conv(
263     char *string)
264 {
265
266     conv_if(SECOND, TMT_SECOND);
267     conv_if(MINUTE, TMT_MINUTE);
268     conv_if(HOUR, TMT_HOUR);
269     conv_if(DAY, TMT_DAY);
270     conv_if(WEEK, TMT_WEEK);
271     conv_if(MONTH, TMT_MONTH);
272     conv_if(YEAR, TMT_YEAR);
273     return (enum tmt_en)(-1);
274 }
275
276 enum grc_en grc_conv(
277     char *string)
278 {
279
280     conv_if(BACK, GRC_BACK);
281     conv_if(CANVAS, GRC_CANVAS);
282     conv_if(SHADEA, GRC_SHADEA);
283     conv_if(SHADEB, GRC_SHADEB);
284     conv_if(GRID, GRC_GRID);
285     conv_if(MGRID, GRC_MGRID);
286     conv_if(FONT, GRC_FONT);
287     conv_if(ARROW, GRC_ARROW);
288     conv_if(AXIS, GRC_AXIS);
289     conv_if(FRAME, GRC_FRAME);
290
291     return (enum grc_en)(-1);
292 }
293
294 enum text_prop_en text_prop_conv(
295     char *string)
296 {
297
298     conv_if(DEFAULT, TEXT_PROP_DEFAULT);
299     conv_if(TITLE, TEXT_PROP_TITLE);
300     conv_if(AXIS, TEXT_PROP_AXIS);
301     conv_if(UNIT, TEXT_PROP_UNIT);
302     conv_if(LEGEND, TEXT_PROP_LEGEND);
303     conv_if(WATERMARK, TEXT_PROP_WATERMARK);
304     return (enum text_prop_en)(-1);
305 }
306
307
308 #undef conv_if
309
310 int im_free(
311     image_desc_t *im)
312 {
313     unsigned long i, ii;
314     cairo_status_t status = (cairo_status_t) 0;
315
316     if (im == NULL)
317         return 0;
318
319     if (im->daemon_addr != NULL)
320       free(im->daemon_addr);
321
322     for (i = 0; i < (unsigned) im->gdes_c; i++) {
323         if (im->gdes[i].data_first) {
324             /* careful here, because a single pointer can occur several times */
325             free(im->gdes[i].data);
326             if (im->gdes[i].ds_namv) {
327                 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
328                     free(im->gdes[i].ds_namv[ii]);
329                 free(im->gdes[i].ds_namv);
330             }
331         }
332         /* free allocated memory used for dashed lines */
333         if (im->gdes[i].p_dashes != NULL)
334             free(im->gdes[i].p_dashes);
335
336         free(im->gdes[i].p_data);
337         free(im->gdes[i].rpnp);
338     }
339     free(im->gdes);
340     if (im->font_options)
341         cairo_font_options_destroy(im->font_options);
342
343     if (im->cr) {
344         status = cairo_status(im->cr);
345         cairo_destroy(im->cr);
346     }
347     if (im->rendered_image) {
348         free(im->rendered_image);
349     }
350
351     if (im->layout) {
352         g_object_unref (im->layout);
353     }
354
355     if (im->surface)
356         cairo_surface_destroy(im->surface);
357
358     if (status)
359         fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
360                 cairo_status_to_string(status));
361
362     return 0;
363 }
364
365 /* find SI magnitude symbol for the given number*/
366 void auto_scale(
367     image_desc_t *im,   /* image description */
368     double *value,
369     char **symb_ptr,
370     double *magfact)
371 {
372
373     char     *symbol[] = { "a", /* 10e-18 Atto */
374         "f",            /* 10e-15 Femto */
375         "p",            /* 10e-12 Pico */
376         "n",            /* 10e-9  Nano */
377         "u",            /* 10e-6  Micro */
378         "m",            /* 10e-3  Milli */
379         " ",            /* Base */
380         "k",            /* 10e3   Kilo */
381         "M",            /* 10e6   Mega */
382         "G",            /* 10e9   Giga */
383         "T",            /* 10e12  Tera */
384         "P",            /* 10e15  Peta */
385         "E"
386     };                  /* 10e18  Exa */
387
388     int       symbcenter = 6;
389     int       sindex;
390
391     if (*value == 0.0 || isnan(*value)) {
392         sindex = 0;
393         *magfact = 1.0;
394     } else {
395         sindex = floor(log(fabs(*value)) / log((double) im->base));
396         *magfact = pow((double) im->base, (double) sindex);
397         (*value) /= (*magfact);
398     }
399     if (sindex <= symbcenter && sindex >= -symbcenter) {
400         (*symb_ptr) = symbol[sindex + symbcenter];
401     } else {
402         (*symb_ptr) = "?";
403     }
404 }
405
406
407 static char si_symbol[] = {
408     'a',                /* 10e-18 Atto */
409     'f',                /* 10e-15 Femto */
410     'p',                /* 10e-12 Pico */
411     'n',                /* 10e-9  Nano */
412     'u',                /* 10e-6  Micro */
413     'm',                /* 10e-3  Milli */
414     ' ',                /* Base */
415     'k',                /* 10e3   Kilo */
416     'M',                /* 10e6   Mega */
417     'G',                /* 10e9   Giga */
418     'T',                /* 10e12  Tera */
419     'P',                /* 10e15  Peta */
420     'E',                /* 10e18  Exa */
421 };
422 static const int si_symbcenter = 6;
423
424 /* find SI magnitude symbol for the numbers on the y-axis*/
425 void si_unit(
426     image_desc_t *im    /* image description */
427     )
428 {
429
430     double    digits, viewdigits = 0;
431
432     digits =
433         floor(log(max(fabs(im->minval), fabs(im->maxval))) /
434               log((double) im->base));
435
436     if (im->unitsexponent != 9999) {
437         /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
438         viewdigits = floor((double)(im->unitsexponent / 3));
439     } else {
440         viewdigits = digits;
441     }
442
443     im->magfact = pow((double) im->base, digits);
444
445 #ifdef DEBUG
446     printf("digits %6.3f  im->magfact %6.3f\n", digits, im->magfact);
447 #endif
448
449     im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
450
451     if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
452         ((viewdigits + si_symbcenter) >= 0))
453         im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
454     else
455         im->symbol = '?';
456 }
457
458 /*  move min and max values around to become sensible */
459
460 void expand_range(
461     image_desc_t *im)
462 {
463     double    sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
464         600.0, 500.0, 400.0, 300.0, 250.0,
465         200.0, 125.0, 100.0, 90.0, 80.0,
466         75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
467         25.0, 20.0, 10.0, 9.0, 8.0,
468         7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
469         2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
470         0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
471     };
472
473     double    scaled_min, scaled_max;
474     double    adj;
475     int       i;
476
477
478
479 #ifdef DEBUG
480     printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
481            im->minval, im->maxval, im->magfact);
482 #endif
483
484     if (isnan(im->ygridstep)) {
485         if (im->extra_flags & ALTAUTOSCALE) {
486             /* measure the amplitude of the function. Make sure that
487                graph boundaries are slightly higher then max/min vals
488                so we can see amplitude on the graph */
489             double    delt, fact;
490
491             delt = im->maxval - im->minval;
492             adj = delt * 0.1;
493             fact = 2.0 * pow(10.0,
494                              floor(log10
495                                    (max(fabs(im->minval), fabs(im->maxval)) /
496                                     im->magfact)) - 2);
497             if (delt < fact) {
498                 adj = (fact - delt) * 0.55;
499 #ifdef DEBUG
500                 printf
501                     ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
502                      im->minval, im->maxval, delt, fact, adj);
503 #endif
504             }
505             im->minval -= adj;
506             im->maxval += adj;
507         } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
508             /* measure the amplitude of the function. Make sure that
509                graph boundaries are slightly lower than min vals
510                so we can see amplitude on the graph */
511             adj = (im->maxval - im->minval) * 0.1;
512             im->minval -= adj;
513         } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
514             /* measure the amplitude of the function. Make sure that
515                graph boundaries are slightly higher than max vals
516                so we can see amplitude on the graph */
517             adj = (im->maxval - im->minval) * 0.1;
518             im->maxval += adj;
519         } else {
520             scaled_min = im->minval / im->magfact;
521             scaled_max = im->maxval / im->magfact;
522
523             for (i = 1; sensiblevalues[i] > 0; i++) {
524                 if (sensiblevalues[i - 1] >= scaled_min &&
525                     sensiblevalues[i] <= scaled_min)
526                     im->minval = sensiblevalues[i] * (im->magfact);
527
528                 if (-sensiblevalues[i - 1] <= scaled_min &&
529                     -sensiblevalues[i] >= scaled_min)
530                     im->minval = -sensiblevalues[i - 1] * (im->magfact);
531
532                 if (sensiblevalues[i - 1] >= scaled_max &&
533                     sensiblevalues[i] <= scaled_max)
534                     im->maxval = sensiblevalues[i - 1] * (im->magfact);
535
536                 if (-sensiblevalues[i - 1] <= scaled_max &&
537                     -sensiblevalues[i] >= scaled_max)
538                     im->maxval = -sensiblevalues[i] * (im->magfact);
539             }
540         }
541     } else {
542         /* adjust min and max to the grid definition if there is one */
543         im->minval = (double) im->ylabfact * im->ygridstep *
544             floor(im->minval / ((double) im->ylabfact * im->ygridstep));
545         im->maxval = (double) im->ylabfact * im->ygridstep *
546             ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
547     }
548
549 #ifdef DEBUG
550     fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
551             im->minval, im->maxval, im->magfact);
552 #endif
553 }
554
555
556 void apply_gridfit(
557     image_desc_t *im)
558 {
559     if (isnan(im->minval) || isnan(im->maxval))
560         return;
561     ytr(im, DNAN);
562     if (im->logarithmic) {
563         double    ya, yb, ypix, ypixfrac;
564         double    log10_range = log10(im->maxval) - log10(im->minval);
565
566         ya = pow((double) 10, floor(log10(im->minval)));
567         while (ya < im->minval)
568             ya *= 10;
569         if (ya > im->maxval)
570             return;     /* don't have y=10^x gridline */
571         yb = ya * 10;
572         if (yb <= im->maxval) {
573             /* we have at least 2 y=10^x gridlines.
574                Make sure distance between them in pixels
575                are an integer by expanding im->maxval */
576             double    y_pixel_delta = ytr(im, ya) - ytr(im, yb);
577             double    factor = y_pixel_delta / floor(y_pixel_delta);
578             double    new_log10_range = factor * log10_range;
579             double    new_ymax_log10 = log10(im->minval) + new_log10_range;
580
581             im->maxval = pow(10, new_ymax_log10);
582             ytr(im, DNAN);  /* reset precalc */
583             log10_range = log10(im->maxval) - log10(im->minval);
584         }
585         /* make sure first y=10^x gridline is located on
586            integer pixel position by moving scale slightly
587            downwards (sub-pixel movement) */
588         ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
589         ypixfrac = ypix - floor(ypix);
590         if (ypixfrac > 0 && ypixfrac < 1) {
591             double    yfrac = ypixfrac / im->ysize;
592
593             im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
594             im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
595             ytr(im, DNAN);  /* reset precalc */
596         }
597     } else {
598         /* Make sure we have an integer pixel distance between
599            each minor gridline */
600         double    ypos1 = ytr(im, im->minval);
601         double    ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
602         double    y_pixel_delta = ypos1 - ypos2;
603         double    factor = y_pixel_delta / floor(y_pixel_delta);
604         double    new_range = factor * (im->maxval - im->minval);
605         double    gridstep = im->ygrid_scale.gridstep;
606         double    minor_y, minor_y_px, minor_y_px_frac;
607
608         if (im->maxval > 0.0)
609             im->maxval = im->minval + new_range;
610         else
611             im->minval = im->maxval - new_range;
612         ytr(im, DNAN);  /* reset precalc */
613         /* make sure first minor gridline is on integer pixel y coord */
614         minor_y = gridstep * floor(im->minval / gridstep);
615         while (minor_y < im->minval)
616             minor_y += gridstep;
617         minor_y_px = ytr(im, minor_y) + im->ysize;  /* ensure > 0 by adding ysize */
618         minor_y_px_frac = minor_y_px - floor(minor_y_px);
619         if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
620             double    yfrac = minor_y_px_frac / im->ysize;
621             double    range = im->maxval - im->minval;
622
623             im->minval = im->minval - yfrac * range;
624             im->maxval = im->maxval - yfrac * range;
625             ytr(im, DNAN);  /* reset precalc */
626         }
627         calc_horizontal_grid(im);   /* recalc with changed im->maxval */
628     }
629 }
630
631 /* reduce data reimplementation by Alex */
632
633 void reduce_data(
634     enum cf_en cf,      /* which consolidation function ? */
635     unsigned long cur_step, /* step the data currently is in */
636     time_t *start,      /* start, end and step as requested ... */
637     time_t *end,        /* ... by the application will be   ... */
638     unsigned long *step,    /* ... adjusted to represent reality    */
639     unsigned long *ds_cnt,  /* number of data sources in file */
640     rrd_value_t **data)
641 {                       /* two dimensional array containing the data */
642     int       i, reduce_factor = ceil((double) (*step) / (double) cur_step);
643     unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
644         0;
645     rrd_value_t *srcptr, *dstptr;
646
647     (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
648     dstptr = *data;
649     srcptr = *data;
650     row_cnt = ((*end) - (*start)) / cur_step;
651
652 #ifdef DEBUG
653 #define DEBUG_REDUCE
654 #endif
655 #ifdef DEBUG_REDUCE
656     printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
657            row_cnt, reduce_factor, *start, *end, cur_step);
658     for (col = 0; col < row_cnt; col++) {
659         printf("time %10lu: ", *start + (col + 1) * cur_step);
660         for (i = 0; i < *ds_cnt; i++)
661             printf(" %8.2e", srcptr[*ds_cnt * col + i]);
662         printf("\n");
663     }
664 #endif
665
666     /* We have to combine [reduce_factor] rows of the source
667      ** into one row for the destination.  Doing this we also
668      ** need to take care to combine the correct rows.  First
669      ** alter the start and end time so that they are multiples
670      ** of the new step time.  We cannot reduce the amount of
671      ** time so we have to move the end towards the future and
672      ** the start towards the past.
673      */
674     end_offset = (*end) % (*step);
675     start_offset = (*start) % (*step);
676
677     /* If there is a start offset (which cannot be more than
678      ** one destination row), skip the appropriate number of
679      ** source rows and one destination row.  The appropriate
680      ** number is what we do know (start_offset/cur_step) of
681      ** the new interval (*step/cur_step aka reduce_factor).
682      */
683 #ifdef DEBUG_REDUCE
684     printf("start_offset: %lu  end_offset: %lu\n", start_offset, end_offset);
685     printf("row_cnt before:  %lu\n", row_cnt);
686 #endif
687     if (start_offset) {
688         (*start) = (*start) - start_offset;
689         skiprows = reduce_factor - start_offset / cur_step;
690         srcptr += skiprows * *ds_cnt;
691         for (col = 0; col < (*ds_cnt); col++)
692             *dstptr++ = DNAN;
693         row_cnt -= skiprows;
694     }
695 #ifdef DEBUG_REDUCE
696     printf("row_cnt between: %lu\n", row_cnt);
697 #endif
698
699     /* At the end we have some rows that are not going to be
700      ** used, the amount is end_offset/cur_step
701      */
702     if (end_offset) {
703         (*end) = (*end) - end_offset + (*step);
704         skiprows = end_offset / cur_step;
705         row_cnt -= skiprows;
706     }
707 #ifdef DEBUG_REDUCE
708     printf("row_cnt after:   %lu\n", row_cnt);
709 #endif
710
711 /* Sanity check: row_cnt should be multiple of reduce_factor */
712 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
713
714     if (row_cnt % reduce_factor) {
715         printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
716                row_cnt, reduce_factor);
717         printf("BUG in reduce_data()\n");
718         exit(1);
719     }
720
721     /* Now combine reduce_factor intervals at a time
722      ** into one interval for the destination.
723      */
724
725     for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
726         for (col = 0; col < (*ds_cnt); col++) {
727             rrd_value_t newval = DNAN;
728             unsigned long validval = 0;
729
730             for (i = 0; i < reduce_factor; i++) {
731                 if (isnan(srcptr[i * (*ds_cnt) + col])) {
732                     continue;
733                 }
734                 validval++;
735                 if (isnan(newval))
736                     newval = srcptr[i * (*ds_cnt) + col];
737                 else {
738                     switch (cf) {
739                     case CF_HWPREDICT:
740                     case CF_MHWPREDICT:
741                     case CF_DEVSEASONAL:
742                     case CF_DEVPREDICT:
743                     case CF_SEASONAL:
744                     case CF_AVERAGE:
745                         newval += srcptr[i * (*ds_cnt) + col];
746                         break;
747                     case CF_MINIMUM:
748                         newval = min(newval, srcptr[i * (*ds_cnt) + col]);
749                         break;
750                     case CF_FAILURES:
751                         /* an interval contains a failure if any subintervals contained a failure */
752                     case CF_MAXIMUM:
753                         newval = max(newval, srcptr[i * (*ds_cnt) + col]);
754                         break;
755                     case CF_LAST:
756                         newval = srcptr[i * (*ds_cnt) + col];
757                         break;
758                     }
759                 }
760             }
761             if (validval == 0) {
762                 newval = DNAN;
763             } else {
764                 switch (cf) {
765                 case CF_HWPREDICT:
766                 case CF_MHWPREDICT:
767                 case CF_DEVSEASONAL:
768                 case CF_DEVPREDICT:
769                 case CF_SEASONAL:
770                 case CF_AVERAGE:
771                     newval /= validval;
772                     break;
773                 case CF_MINIMUM:
774                 case CF_FAILURES:
775                 case CF_MAXIMUM:
776                 case CF_LAST:
777                     break;
778                 }
779             }
780             *dstptr++ = newval;
781         }
782         srcptr += (*ds_cnt) * reduce_factor;
783         row_cnt -= reduce_factor;
784     }
785     /* If we had to alter the endtime, we didn't have enough
786      ** source rows to fill the last row. Fill it with NaN.
787      */
788     if (end_offset)
789         for (col = 0; col < (*ds_cnt); col++)
790             *dstptr++ = DNAN;
791 #ifdef DEBUG_REDUCE
792     row_cnt = ((*end) - (*start)) / *step;
793     srcptr = *data;
794     printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
795            row_cnt, *start, *end, *step);
796     for (col = 0; col < row_cnt; col++) {
797         printf("time %10lu: ", *start + (col + 1) * (*step));
798         for (i = 0; i < *ds_cnt; i++)
799             printf(" %8.2e", srcptr[*ds_cnt * col + i]);
800         printf("\n");
801     }
802 #endif
803 }
804
805
806 /* get the data required for the graphs from the
807    relevant rrds ... */
808
809 int data_fetch(
810     image_desc_t *im)
811 {
812     int       i, ii;
813     int       skip;
814
815     /* pull the data from the rrd files ... */
816     for (i = 0; i < (int) im->gdes_c; i++) {
817         /* only GF_DEF elements fetch data */
818         if (im->gdes[i].gf != GF_DEF)
819             continue;
820
821         skip = 0;
822         /* do we have it already ? */
823         for (ii = 0; ii < i; ii++) {
824             if (im->gdes[ii].gf != GF_DEF)
825                 continue;
826             if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
827                 && (im->gdes[i].cf == im->gdes[ii].cf)
828                 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
829                 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
830                 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
831                 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
832                 /* OK, the data is already there.
833                  ** Just copy the header portion
834                  */
835                 im->gdes[i].start = im->gdes[ii].start;
836                 im->gdes[i].end = im->gdes[ii].end;
837                 im->gdes[i].step = im->gdes[ii].step;
838                 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
839                 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
840                 im->gdes[i].data = im->gdes[ii].data;
841                 im->gdes[i].data_first = 0;
842                 skip = 1;
843             }
844             if (skip)
845                 break;
846         }
847         if (!skip) {
848             unsigned long ft_step = im->gdes[i].step;   /* ft_step will record what we got from fetch */
849             const char *rrd_daemon;
850             int status;
851
852             if (im->gdes[i].daemon[0] != 0)
853                 rrd_daemon = im->gdes[i].daemon;
854             else
855                 rrd_daemon = im->daemon_addr;
856
857             /* "daemon" may be NULL. ENV_RRDCACHED_ADDRESS is evaluated in that
858              * case. If "daemon" holds the same value as in the previous
859              * iteration, no actual new connection is established - the
860              * existing connection is re-used. */
861             rrdc_connect (rrd_daemon);
862
863             /* If connecting was successfull, use the daemon to query the data.
864              * If there is no connection, for example because no daemon address
865              * was specified, (try to) use the local file directly. */
866             if (rrdc_is_connected (rrd_daemon))
867             {
868                 status = rrdc_fetch (im->gdes[i].rrd,
869                         cf_to_string (im->gdes[i].cf),
870                         &im->gdes[i].start,
871                         &im->gdes[i].end,
872                         &ft_step,
873                         &im->gdes[i].ds_cnt,
874                         &im->gdes[i].ds_namv,
875                         &im->gdes[i].data);
876                 if (status != 0)
877                     return (status);
878             }
879             else
880             {
881                 if ((rrd_fetch_fn(im->gdes[i].rrd,
882                                 im->gdes[i].cf,
883                                 &im->gdes[i].start,
884                                 &im->gdes[i].end,
885                                 &ft_step,
886                                 &im->gdes[i].ds_cnt,
887                                 &im->gdes[i].ds_namv,
888                                 &im->gdes[i].data)) == -1) {
889                     return -1;
890                 }
891             }
892             im->gdes[i].data_first = 1;
893
894             if (ft_step < im->gdes[i].step) {
895                 reduce_data(im->gdes[i].cf_reduce,
896                             ft_step,
897                             &im->gdes[i].start,
898                             &im->gdes[i].end,
899                             &im->gdes[i].step,
900                             &im->gdes[i].ds_cnt, &im->gdes[i].data);
901             } else {
902                 im->gdes[i].step = ft_step;
903             }
904         }
905
906         /* lets see if the required data source is really there */
907         for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
908             if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
909                 im->gdes[i].ds = ii;
910             }
911         }
912         if (im->gdes[i].ds == -1) {
913             rrd_set_error("No DS called '%s' in '%s'",
914                           im->gdes[i].ds_nam, im->gdes[i].rrd);
915             return -1;
916         }
917
918     }
919     return 0;
920 }
921
922 /* evaluate the expressions in the CDEF functions */
923
924 /*************************************************************
925  * CDEF stuff
926  *************************************************************/
927
928 long find_var_wrapper(
929     void *arg1,
930     char *key)
931 {
932     return find_var((image_desc_t *) arg1, key);
933 }
934
935 /* find gdes containing var*/
936 long find_var(
937     image_desc_t *im,
938     char *key)
939 {
940     long      ii;
941
942     for (ii = 0; ii < im->gdes_c - 1; ii++) {
943         if ((im->gdes[ii].gf == GF_DEF
944              || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
945             && (strcmp(im->gdes[ii].vname, key) == 0)) {
946             return ii;
947         }
948     }
949     return -1;
950 }
951
952 /* find the greatest common divisor for all the numbers
953    in the 0 terminated num array */
954 long lcd(
955     long *num)
956 {
957     long      rest;
958     int       i;
959
960     for (i = 0; num[i + 1] != 0; i++) {
961         do {
962             rest = num[i] % num[i + 1];
963             num[i] = num[i + 1];
964             num[i + 1] = rest;
965         } while (rest != 0);
966         num[i + 1] = num[i];
967     }
968 /*    return i==0?num[i]:num[i-1]; */
969     return num[i];
970 }
971
972 /* run the rpn calculator on all the VDEF and CDEF arguments */
973 int data_calc(
974     image_desc_t *im)
975 {
976
977     int       gdi;
978     int       dataidx;
979     long     *steparray, rpi;
980     int       stepcnt;
981     time_t    now;
982     rpnstack_t rpnstack;
983
984     rpnstack_init(&rpnstack);
985
986     for (gdi = 0; gdi < im->gdes_c; gdi++) {
987         /* Look for GF_VDEF and GF_CDEF in the same loop,
988          * so CDEFs can use VDEFs and vice versa
989          */
990         switch (im->gdes[gdi].gf) {
991         case GF_XPORT:
992             break;
993         case GF_SHIFT:{
994             graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
995
996             /* remove current shift */
997             vdp->start -= vdp->shift;
998             vdp->end -= vdp->shift;
999
1000             /* vdef */
1001             if (im->gdes[gdi].shidx >= 0)
1002                 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
1003             /* constant */
1004             else
1005                 vdp->shift = im->gdes[gdi].shval;
1006
1007             /* normalize shift to multiple of consolidated step */
1008             vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
1009
1010             /* apply shift */
1011             vdp->start += vdp->shift;
1012             vdp->end += vdp->shift;
1013             break;
1014         }
1015         case GF_VDEF:
1016             /* A VDEF has no DS.  This also signals other parts
1017              * of rrdtool that this is a VDEF value, not a CDEF.
1018              */
1019             im->gdes[gdi].ds_cnt = 0;
1020             if (vdef_calc(im, gdi)) {
1021                 rrd_set_error("Error processing VDEF '%s'",
1022                               im->gdes[gdi].vname);
1023                 rpnstack_free(&rpnstack);
1024                 return -1;
1025             }
1026             break;
1027         case GF_CDEF:
1028             im->gdes[gdi].ds_cnt = 1;
1029             im->gdes[gdi].ds = 0;
1030             im->gdes[gdi].data_first = 1;
1031             im->gdes[gdi].start = 0;
1032             im->gdes[gdi].end = 0;
1033             steparray = NULL;
1034             stepcnt = 0;
1035             dataidx = -1;
1036
1037             /* Find the variables in the expression.
1038              * - VDEF variables are substituted by their values
1039              *   and the opcode is changed into OP_NUMBER.
1040              * - CDEF variables are analized for their step size,
1041              *   the lowest common denominator of all the step
1042              *   sizes of the data sources involved is calculated
1043              *   and the resulting number is the step size for the
1044              *   resulting data source.
1045              */
1046             for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1047                 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1048                     im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1049                     long      ptr = im->gdes[gdi].rpnp[rpi].ptr;
1050
1051                     if (im->gdes[ptr].ds_cnt == 0) {    /* this is a VDEF data source */
1052 #if 0
1053                         printf
1054                             ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1055                              im->gdes[gdi].vname, im->gdes[ptr].vname);
1056                         printf("DEBUG: value from vdef is %f\n",
1057                                im->gdes[ptr].vf.val);
1058 #endif
1059                         im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1060                         im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1061                     } else {    /* normal variables and PREF(variables) */
1062
1063                         /* add one entry to the array that keeps track of the step sizes of the
1064                          * data sources going into the CDEF. */
1065                         if ((steparray =
1066                              (long*)rrd_realloc(steparray,
1067                                          (++stepcnt +
1068                                           1) * sizeof(*steparray))) == NULL) {
1069                             rrd_set_error("realloc steparray");
1070                             rpnstack_free(&rpnstack);
1071                             return -1;
1072                         };
1073
1074                         steparray[stepcnt - 1] = im->gdes[ptr].step;
1075
1076                         /* adjust start and end of cdef (gdi) so
1077                          * that it runs from the latest start point
1078                          * to the earliest endpoint of any of the
1079                          * rras involved (ptr)
1080                          */
1081
1082                         if (im->gdes[gdi].start < im->gdes[ptr].start)
1083                             im->gdes[gdi].start = im->gdes[ptr].start;
1084
1085                         if (im->gdes[gdi].end == 0 ||
1086                             im->gdes[gdi].end > im->gdes[ptr].end)
1087                             im->gdes[gdi].end = im->gdes[ptr].end;
1088
1089                         /* store pointer to the first element of
1090                          * the rra providing data for variable,
1091                          * further save step size and data source
1092                          * count of this rra
1093                          */
1094                         im->gdes[gdi].rpnp[rpi].data =
1095                             im->gdes[ptr].data + im->gdes[ptr].ds;
1096                         im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1097                         im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1098
1099                         /* backoff the *.data ptr; this is done so
1100                          * rpncalc() function doesn't have to treat
1101                          * the first case differently
1102                          */
1103                     }   /* if ds_cnt != 0 */
1104                 }       /* if OP_VARIABLE */
1105             }           /* loop through all rpi */
1106
1107             /* move the data pointers to the correct period */
1108             for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1109                 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1110                     im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1111                     long      ptr = im->gdes[gdi].rpnp[rpi].ptr;
1112                     long      diff =
1113                         im->gdes[gdi].start - im->gdes[ptr].start;
1114
1115                     if (diff > 0)
1116                         im->gdes[gdi].rpnp[rpi].data +=
1117                             (diff / im->gdes[ptr].step) *
1118                             im->gdes[ptr].ds_cnt;
1119                 }
1120             }
1121
1122             if (steparray == NULL) {
1123                 rrd_set_error("rpn expressions without DEF"
1124                               " or CDEF variables are not supported");
1125                 rpnstack_free(&rpnstack);
1126                 return -1;
1127             }
1128             steparray[stepcnt] = 0;
1129             /* Now find the resulting step.  All steps in all
1130              * used RRAs have to be visited
1131              */
1132             im->gdes[gdi].step = lcd(steparray);
1133             free(steparray);
1134             if ((im->gdes[gdi].data = (rrd_value_t*)malloc(((im->gdes[gdi].end -
1135                                                im->gdes[gdi].start)
1136                                               / im->gdes[gdi].step)
1137                                              * sizeof(double))) == NULL) {
1138                 rrd_set_error("malloc im->gdes[gdi].data");
1139                 rpnstack_free(&rpnstack);
1140                 return -1;
1141             }
1142
1143             /* Step through the new cdef results array and
1144              * calculate the values
1145              */
1146             for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1147                  now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1148                 rpnp_t   *rpnp = im->gdes[gdi].rpnp;
1149
1150                 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1151                  * in this case we are advancing by timesteps;
1152                  * we use the fact that time_t is a synonym for long
1153                  */
1154                 if (rpn_calc(rpnp, &rpnstack, (long) now,
1155                              im->gdes[gdi].data, ++dataidx) == -1) {
1156                     /* rpn_calc sets the error string */
1157                     rpnstack_free(&rpnstack);
1158                     return -1;
1159                 }
1160             }           /* enumerate over time steps within a CDEF */
1161             break;
1162         default:
1163             continue;
1164         }
1165     }                   /* enumerate over CDEFs */
1166     rpnstack_free(&rpnstack);
1167     return 0;
1168 }
1169
1170 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1171 /* yes we are loosing precision by doing tos with floats instead of doubles
1172    but it seems more stable this way. */
1173
1174 static int AlmostEqual2sComplement(
1175     float A,
1176     float B,
1177     int maxUlps)
1178 {
1179
1180     int       aInt = *(int *) &A;
1181     int       bInt = *(int *) &B;
1182     int       intDiff;
1183
1184     /* Make sure maxUlps is non-negative and small enough that the
1185        default NAN won't compare as equal to anything.  */
1186
1187     /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1188
1189     /* Make aInt lexicographically ordered as a twos-complement int */
1190
1191     if (aInt < 0)
1192         aInt = 0x80000000l - aInt;
1193
1194     /* Make bInt lexicographically ordered as a twos-complement int */
1195
1196     if (bInt < 0)
1197         bInt = 0x80000000l - bInt;
1198
1199     intDiff = abs(aInt - bInt);
1200
1201     if (intDiff <= maxUlps)
1202         return 1;
1203
1204     return 0;
1205 }
1206
1207 /* massage data so, that we get one value for each x coordinate in the graph */
1208 int data_proc(
1209     image_desc_t *im)
1210 {
1211     long      i, ii;
1212     double    pixstep = (double) (im->end - im->start)
1213         / (double) im->xsize;   /* how much time
1214                                    passes in one pixel */
1215     double    paintval;
1216     double    minval = DNAN, maxval = DNAN;
1217
1218     unsigned long gr_time;
1219
1220     /* memory for the processed data */
1221     for (i = 0; i < im->gdes_c; i++) {
1222         if ((im->gdes[i].gf == GF_LINE)
1223          || (im->gdes[i].gf == GF_AREA) 
1224          || (im->gdes[i].gf == GF_TICK)
1225          || (im->gdes[i].gf == GF_GRAD)
1226         ) {
1227             if ((im->gdes[i].p_data = (rrd_value_t*)malloc((im->xsize + 1)
1228                                              * sizeof(rrd_value_t))) == NULL) {
1229                 rrd_set_error("malloc data_proc");
1230                 return -1;
1231             }
1232         }
1233     }
1234
1235     for (i = 0; i < im->xsize; i++) {   /* for each pixel */
1236         long      vidx;
1237
1238         gr_time = im->start + pixstep * i;  /* time of the current step */
1239         paintval = 0.0;
1240
1241         for (ii = 0; ii < im->gdes_c; ii++) {
1242             double    value;
1243
1244             switch (im->gdes[ii].gf) {
1245             case GF_LINE:
1246             case GF_AREA:
1247                         case GF_GRAD:
1248             case GF_TICK:
1249                 if (!im->gdes[ii].stack)
1250                     paintval = 0.0;
1251                 value = im->gdes[ii].yrule;
1252                 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1253                     /* The time of the data doesn't necessarily match
1254                      ** the time of the graph. Beware.
1255                      */
1256                     vidx = im->gdes[ii].vidx;
1257                     if (im->gdes[vidx].gf == GF_VDEF) {
1258                         value = im->gdes[vidx].vf.val;
1259                     } else
1260                         if (((long int) gr_time >=
1261                              (long int) im->gdes[vidx].start)
1262                             && ((long int) gr_time <
1263                                 (long int) im->gdes[vidx].end)) {
1264                         value = im->gdes[vidx].data[(unsigned long)
1265                                                     floor((double)
1266                                                           (gr_time -
1267                                                            im->gdes[vidx].
1268                                                            start)
1269                                                           /
1270                                                           im->gdes[vidx].step)
1271                                                     * im->gdes[vidx].ds_cnt +
1272                                                     im->gdes[vidx].ds];
1273                     } else {
1274                         value = DNAN;
1275                     }
1276                 };
1277
1278                 if (!isnan(value)) {
1279                     paintval += value;
1280                     im->gdes[ii].p_data[i] = paintval;
1281                     /* GF_TICK: the data values are not
1282                      ** relevant for min and max
1283                      */
1284                     if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1285                         if ((isnan(minval) || paintval < minval) &&
1286                             !(im->logarithmic && paintval <= 0.0))
1287                             minval = paintval;
1288                         if (isnan(maxval) || paintval > maxval)
1289                             maxval = paintval;
1290                     }
1291                 } else {
1292                     im->gdes[ii].p_data[i] = DNAN;
1293                 }
1294                 break;
1295             case GF_STACK:
1296                 rrd_set_error
1297                     ("STACK should already be turned into LINE or AREA here");
1298                 return -1;
1299                 break;
1300             default:
1301                 break;
1302             }
1303         }
1304     }
1305
1306     /* if min or max have not been asigned a value this is because
1307        there was no data in the graph ... this is not good ...
1308        lets set these to dummy values then ... */
1309
1310     if (im->logarithmic) {
1311         if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1312             minval = 0.0;   /* catching this right away below */
1313             maxval = 5.1;
1314         }
1315         /* in logarithm mode, where minval is smaller or equal
1316            to 0 make the beast just way smaller than maxval */
1317         if (minval <= 0) {
1318             minval = maxval / 10e8;
1319         }
1320     } else {
1321         if (isnan(minval) || isnan(maxval)) {
1322             minval = 0.0;
1323             maxval = 1.0;
1324         }
1325     }
1326
1327     /* adjust min and max values given by the user */
1328     /* for logscale we add something on top */
1329     if (isnan(im->minval)
1330         || ((!im->rigid) && im->minval > minval)
1331         ) {
1332         if (im->logarithmic)
1333             im->minval = minval / 2.0;
1334         else
1335             im->minval = minval;
1336     }
1337     if (isnan(im->maxval)
1338         || (!im->rigid && im->maxval < maxval)
1339         ) {
1340         if (im->logarithmic)
1341             im->maxval = maxval * 2.0;
1342         else
1343             im->maxval = maxval;
1344     }
1345
1346     /* make sure min is smaller than max */
1347     if (im->minval > im->maxval) {
1348         if (im->minval > 0)
1349             im->minval = 0.99 * im->maxval;
1350         else
1351             im->minval = 1.01 * im->maxval;
1352     }
1353
1354     /* make sure min and max are not equal */
1355     if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1356         if (im->maxval > 0)
1357             im->maxval *= 1.01;
1358         else
1359             im->maxval *= 0.99;
1360
1361         /* make sure min and max are not both zero */
1362         if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1363             im->maxval = 1.0;
1364         }
1365     }
1366     return 0;
1367 }
1368
1369 static int find_first_weekday(void){
1370     static int first_weekday = -1;
1371     if (first_weekday == -1){
1372 #ifdef HAVE__NL_TIME_WEEK_1STDAY
1373         /* according to http://sourceware.org/ml/libc-locales/2009-q1/msg00011.html */
1374         long week_1stday_l = (long) nl_langinfo (_NL_TIME_WEEK_1STDAY);
1375         if (week_1stday_l == 19971130) first_weekday = 0; /* Sun */
1376         else if (week_1stday_l == 19971201) first_weekday = 1; /* Mon */
1377         else first_weekday = 1; /* we go for a monday default */
1378 #else
1379         first_weekday = 1;
1380 #endif
1381     }
1382     return first_weekday;
1383 }
1384
1385 /* identify the point where the first gridline, label ... gets placed */
1386
1387 time_t find_first_time(
1388     time_t start,       /* what is the initial time */
1389     enum tmt_en baseint,    /* what is the basic interval */
1390     long basestep       /* how many if these do we jump a time */
1391     )
1392 {
1393     struct tm tm;
1394
1395     localtime_r(&start, &tm);
1396
1397     switch (baseint) {
1398     case TMT_SECOND:
1399         tm.       tm_sec -= tm.tm_sec % basestep;
1400
1401         break;
1402     case TMT_MINUTE:
1403         tm.       tm_sec = 0;
1404         tm.       tm_min -= tm.tm_min % basestep;
1405
1406         break;
1407     case TMT_HOUR:
1408         tm.       tm_sec = 0;
1409         tm.       tm_min = 0;
1410         tm.       tm_hour -= tm.tm_hour % basestep;
1411
1412         break;
1413     case TMT_DAY:
1414         /* we do NOT look at the basestep for this ... */
1415         tm.       tm_sec = 0;
1416         tm.       tm_min = 0;
1417         tm.       tm_hour = 0;
1418
1419         break;
1420     case TMT_WEEK:
1421         /* we do NOT look at the basestep for this ... */
1422         tm.       tm_sec = 0;
1423         tm.       tm_min = 0;
1424         tm.       tm_hour = 0;
1425         tm.       tm_mday -= tm.tm_wday - find_first_weekday();
1426
1427         if (tm.tm_wday == 0 && find_first_weekday() > 0)
1428             tm.       tm_mday -= 7; /* we want the *previous* week */
1429
1430         break;
1431     case TMT_MONTH:
1432         tm.       tm_sec = 0;
1433         tm.       tm_min = 0;
1434         tm.       tm_hour = 0;
1435         tm.       tm_mday = 1;
1436         tm.       tm_mon -= tm.tm_mon % basestep;
1437
1438         break;
1439
1440     case TMT_YEAR:
1441         tm.       tm_sec = 0;
1442         tm.       tm_min = 0;
1443         tm.       tm_hour = 0;
1444         tm.       tm_mday = 1;
1445         tm.       tm_mon = 0;
1446         tm.       tm_year -= (
1447     tm.tm_year + 1900) %basestep;
1448
1449     }
1450     return mktime(&tm);
1451 }
1452
1453 /* identify the point where the next gridline, label ... gets placed */
1454 time_t find_next_time(
1455     time_t current,     /* what is the initial time */
1456     enum tmt_en baseint,    /* what is the basic interval */
1457     long basestep       /* how many if these do we jump a time */
1458     )
1459 {
1460     struct tm tm;
1461     time_t    madetime;
1462
1463     localtime_r(&current, &tm);
1464
1465     do {
1466         switch (baseint) {
1467         case TMT_SECOND:
1468             tm.       tm_sec += basestep;
1469
1470             break;
1471         case TMT_MINUTE:
1472             tm.       tm_min += basestep;
1473
1474             break;
1475         case TMT_HOUR:
1476             tm.       tm_hour += basestep;
1477
1478             break;
1479         case TMT_DAY:
1480             tm.       tm_mday += basestep;
1481
1482             break;
1483         case TMT_WEEK:
1484             tm.       tm_mday += 7 * basestep;
1485
1486             break;
1487         case TMT_MONTH:
1488             tm.       tm_mon += basestep;
1489
1490             break;
1491         case TMT_YEAR:
1492             tm.       tm_year += basestep;
1493         }
1494         madetime = mktime(&tm);
1495     } while (madetime == -1);   /* this is necessary to skip impssible times
1496                                    like the daylight saving time skips */
1497     return madetime;
1498
1499 }
1500
1501
1502 /* calculate values required for PRINT and GPRINT functions */
1503
1504 int print_calc(
1505     image_desc_t *im)
1506 {
1507     long      i, ii, validsteps;
1508     double    printval;
1509     struct tm tmvdef;
1510     int       graphelement = 0;
1511     long      vidx;
1512     int       max_ii;
1513     double    magfact = -1;
1514     char     *si_symb = "";
1515     char     *percent_s;
1516     int       prline_cnt = 0;
1517
1518     /* wow initializing tmvdef is quite a task :-) */
1519     time_t    now = time(NULL);
1520
1521     localtime_r(&now, &tmvdef);
1522     for (i = 0; i < im->gdes_c; i++) {
1523         vidx = im->gdes[i].vidx;
1524         switch (im->gdes[i].gf) {
1525         case GF_PRINT:
1526         case GF_GPRINT:
1527             /* PRINT and GPRINT can now print VDEF generated values.
1528              * There's no need to do any calculations on them as these
1529              * calculations were already made.
1530              */
1531             if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1532                 printval = im->gdes[vidx].vf.val;
1533                 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1534             } else {    /* need to calculate max,min,avg etcetera */
1535                 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1536                           / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1537                 printval = DNAN;
1538                 validsteps = 0;
1539                 for (ii = im->gdes[vidx].ds;
1540                      ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1541                     if (!finite(im->gdes[vidx].data[ii]))
1542                         continue;
1543                     if (isnan(printval)) {
1544                         printval = im->gdes[vidx].data[ii];
1545                         validsteps++;
1546                         continue;
1547                     }
1548
1549                     switch (im->gdes[i].cf) {
1550                     case CF_HWPREDICT:
1551                     case CF_MHWPREDICT:
1552                     case CF_DEVPREDICT:
1553                     case CF_DEVSEASONAL:
1554                     case CF_SEASONAL:
1555                     case CF_AVERAGE:
1556                         validsteps++;
1557                         printval += im->gdes[vidx].data[ii];
1558                         break;
1559                     case CF_MINIMUM:
1560                         printval = min(printval, im->gdes[vidx].data[ii]);
1561                         break;
1562                     case CF_FAILURES:
1563                     case CF_MAXIMUM:
1564                         printval = max(printval, im->gdes[vidx].data[ii]);
1565                         break;
1566                     case CF_LAST:
1567                         printval = im->gdes[vidx].data[ii];
1568                     }
1569                 }
1570                 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1571                     if (validsteps > 1) {
1572                         printval = (printval / validsteps);
1573                     }
1574                 }
1575             }           /* prepare printval */
1576
1577             if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1578                 /* Magfact is set to -1 upon entry to print_calc.  If it
1579                  * is still less than 0, then we need to run auto_scale.
1580                  * Otherwise, put the value into the correct units.  If
1581                  * the value is 0, then do not set the symbol or magnification
1582                  * so next the calculation will be performed again. */
1583                 if (magfact < 0.0) {
1584                     auto_scale(im, &printval, &si_symb, &magfact);
1585                     if (printval == 0.0)
1586                         magfact = -1.0;
1587                 } else {
1588                     printval /= magfact;
1589                 }
1590                 *(++percent_s) = 's';
1591             } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1592                 auto_scale(im, &printval, &si_symb, &magfact);
1593             }
1594
1595             if (im->gdes[i].gf == GF_PRINT) {
1596                 rrd_infoval_t prline;
1597
1598                 if (im->gdes[i].strftm) {
1599                     prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1600                     if (im->gdes[vidx].vf.never == 1) {
1601                        time_clean(prline.u_str, im->gdes[i].format);
1602                     } else {
1603                         strftime(prline.u_str,
1604                                  FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1605                     }
1606                 } else if (bad_format(im->gdes[i].format)) {
1607                     rrd_set_error
1608                         ("bad format for PRINT in '%s'", im->gdes[i].format);
1609                     return -1;
1610                 } else {
1611                     prline.u_str =
1612                         sprintf_alloc(im->gdes[i].format, printval, si_symb);
1613                 }
1614                 grinfo_push(im,
1615                             sprintf_alloc
1616                             ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1617                 free(prline.u_str);
1618             } else {
1619                 /* GF_GPRINT */
1620
1621                 if (im->gdes[i].strftm) {
1622                     if (im->gdes[vidx].vf.never == 1) {
1623                        time_clean(im->gdes[i].legend, im->gdes[i].format);
1624                     } else {
1625                         strftime(im->gdes[i].legend,
1626                                  FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1627                     }
1628                 } else {
1629                     if (bad_format(im->gdes[i].format)) {
1630                         rrd_set_error
1631                             ("bad format for GPRINT in '%s'",
1632                              im->gdes[i].format);
1633                         return -1;
1634                     }
1635 #ifdef HAVE_SNPRINTF
1636                     snprintf(im->gdes[i].legend,
1637                              FMT_LEG_LEN - 2,
1638                              im->gdes[i].format, printval, si_symb);
1639 #else
1640                     sprintf(im->gdes[i].legend,
1641                             im->gdes[i].format, printval, si_symb);
1642 #endif
1643                 }
1644                 graphelement = 1;
1645             }
1646             break;
1647         case GF_LINE:
1648         case GF_AREA:
1649                 case GF_GRAD:
1650         case GF_TICK:
1651             graphelement = 1;
1652             break;
1653         case GF_HRULE:
1654             if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1655                 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1656             };
1657             graphelement = 1;
1658             break;
1659         case GF_VRULE:
1660             if (im->gdes[i].xrule == 0) {   /* again ... the legend printer needs it */
1661                 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1662             };
1663             graphelement = 1;
1664             break;
1665         case GF_COMMENT:
1666         case GF_TEXTALIGN:
1667         case GF_DEF:
1668         case GF_CDEF:
1669         case GF_VDEF:
1670 #ifdef WITH_PIECHART
1671         case GF_PART:
1672 #endif
1673         case GF_SHIFT:
1674         case GF_XPORT:
1675             break;
1676         case GF_STACK:
1677             rrd_set_error
1678                 ("STACK should already be turned into LINE or AREA here");
1679             return -1;
1680             break;
1681         }
1682     }
1683     return graphelement;
1684 }
1685
1686
1687
1688 /* place legends with color spots */
1689 int leg_place(
1690     image_desc_t *im,
1691     int calc_width)
1692 {
1693     /* graph labels */
1694     int       interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1695     int       border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1696     int       fill = 0, fill_last;
1697     double    legendwidth; // = im->ximg - 2 * border;
1698     int       leg_c = 0;
1699     double    leg_x = border;
1700     int       leg_y = 0; //im->yimg;
1701     int       leg_y_prev = 0; // im->yimg;
1702     int       leg_cc;
1703     double    glue = 0;
1704     int       i, ii, mark = 0;
1705     char      default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1706     int      *legspace;
1707     char     *tab;
1708     char      saved_legend[FMT_LEG_LEN + 5];
1709
1710     if(calc_width){
1711         legendwidth = 0;
1712     }
1713     else{
1714         legendwidth = im->legendwidth - 2 * border;
1715     }
1716
1717
1718     if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1719         if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1720             rrd_set_error("malloc for legspace");
1721             return -1;
1722         }
1723
1724         for (i = 0; i < im->gdes_c; i++) {
1725             char      prt_fctn; /*special printfunctions */
1726             if(calc_width){
1727                 strcpy(saved_legend, im->gdes[i].legend);
1728             }
1729
1730             fill_last = fill;
1731             /* hide legends for rules which are not displayed */
1732             if (im->gdes[i].gf == GF_TEXTALIGN) {
1733                 default_txtalign = im->gdes[i].txtalign;
1734             }
1735
1736             if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1737                 if (im->gdes[i].gf == GF_HRULE
1738                     && (im->gdes[i].yrule <
1739                         im->minval || im->gdes[i].yrule > im->maxval))
1740                     im->gdes[i].legend[0] = '\0';
1741                 if (im->gdes[i].gf == GF_VRULE
1742                     && (im->gdes[i].xrule <
1743                         im->start || im->gdes[i].xrule > im->end))
1744                     im->gdes[i].legend[0] = '\0';
1745             }
1746
1747             /* turn \\t into tab */
1748             while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1749                 memmove(tab, tab + 1, strlen(tab));
1750                 tab[0] = (char) 9;
1751             }
1752
1753             leg_cc = strlen(im->gdes[i].legend);
1754             /* is there a controle code at the end of the legend string ? */
1755             if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1756                 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1757                 leg_cc -= 2;
1758                 im->gdes[i].legend[leg_cc] = '\0';
1759             } else {
1760                 prt_fctn = '\0';
1761             }
1762             /* only valid control codes */
1763             if (prt_fctn != 'l' && prt_fctn != 'n' &&   /* a synonym for l */
1764                 prt_fctn != 'r' &&
1765                 prt_fctn != 'j' &&
1766                 prt_fctn != 'c' &&
1767                 prt_fctn != 'u' &&
1768                 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1769                 free(legspace);
1770                 rrd_set_error
1771                     ("Unknown control code at the end of '%s\\%c'",
1772                      im->gdes[i].legend, prt_fctn);
1773                 return -1;
1774             }
1775             /* \n -> \l */
1776             if (prt_fctn == 'n') {
1777                 prt_fctn = 'l';
1778             }
1779
1780             /* remove exess space from the end of the legend for \g */
1781             while (prt_fctn == 'g' &&
1782                    leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1783                 leg_cc--;
1784                 im->gdes[i].legend[leg_cc] = '\0';
1785             }
1786
1787             if (leg_cc != 0) {
1788
1789                 /* no interleg space if string ends in \g */
1790                 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1791                 if (fill > 0) {
1792                     fill += legspace[i];
1793                 }
1794                 fill +=
1795                     gfx_get_text_width(im,
1796                                        fill + border,
1797                                        im->
1798                                        text_prop
1799                                        [TEXT_PROP_LEGEND].
1800                                        font_desc,
1801                                        im->tabwidth, im->gdes[i].legend);
1802                 leg_c++;
1803             } else {
1804                 legspace[i] = 0;
1805             }
1806             /* who said there was a special tag ... ? */
1807             if (prt_fctn == 'g') {
1808                 prt_fctn = '\0';
1809             }
1810
1811             if (prt_fctn == '\0') {
1812                 if(calc_width && (fill > legendwidth)){
1813                     legendwidth = fill;
1814                 }
1815                 if (i == im->gdes_c - 1 || fill > legendwidth) {
1816                     /* just one legend item is left right or center */
1817                     switch (default_txtalign) {
1818                     case TXA_RIGHT:
1819                         prt_fctn = 'r';
1820                         break;
1821                     case TXA_CENTER:
1822                         prt_fctn = 'c';
1823                         break;
1824                     case TXA_JUSTIFIED:
1825                         prt_fctn = 'j';
1826                         break;
1827                     default:
1828                         prt_fctn = 'l';
1829                         break;
1830                     }
1831                 }
1832                 /* is it time to place the legends ? */
1833                 if (fill > legendwidth) {
1834                     if (leg_c > 1) {
1835                         /* go back one */
1836                         i--;
1837                         fill = fill_last;
1838                         leg_c--;
1839                     }
1840                 }
1841                 if (leg_c == 1 && prt_fctn == 'j') {
1842                     prt_fctn = 'l';
1843                 }
1844             }
1845
1846             if (prt_fctn != '\0') {
1847                 leg_x = border;
1848                 if (leg_c >= 2 && prt_fctn == 'j') {
1849                     glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1850                 } else {
1851                     glue = 0;
1852                 }
1853                 if (prt_fctn == 'c')
1854                     leg_x = (double)(legendwidth - fill) / 2.0;
1855                 if (prt_fctn == 'r')
1856                     leg_x = legendwidth - fill + border;
1857                 for (ii = mark; ii <= i; ii++) {
1858                     if (im->gdes[ii].legend[0] == '\0')
1859                         continue;   /* skip empty legends */
1860                     im->gdes[ii].leg_x = leg_x;
1861                     im->gdes[ii].leg_y = leg_y + border;
1862                     leg_x +=
1863                         (double)gfx_get_text_width(im, leg_x,
1864                                            im->
1865                                            text_prop
1866                                            [TEXT_PROP_LEGEND].
1867                                            font_desc,
1868                                            im->tabwidth, im->gdes[ii].legend)
1869                         +(double)legspace[ii]
1870                         + glue;
1871                 }
1872                 leg_y_prev = leg_y;
1873                 if (leg_x > border || prt_fctn == 's')
1874                     leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1875                 if (prt_fctn == 's')
1876                     leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1877                 if (prt_fctn == 'u')
1878                     leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1879
1880                 if(calc_width && (fill > legendwidth)){
1881                     legendwidth = fill;
1882                 }
1883                 fill = 0;
1884                 leg_c = 0;
1885                 mark = ii;
1886             }
1887
1888             if(calc_width){
1889                 strcpy(im->gdes[i].legend, saved_legend);
1890             }
1891         }
1892
1893         if(calc_width){
1894             im->legendwidth = legendwidth + 2 * border;
1895         }
1896         else{
1897             im->legendheight = leg_y + border * 0.6;
1898         }
1899         free(legspace);
1900     }
1901     return 0;
1902 }
1903
1904 /* create a grid on the graph. it determines what to do
1905    from the values of xsize, start and end */
1906
1907 /* the xaxis labels are determined from the number of seconds per pixel
1908    in the requested graph */
1909
1910 int calc_horizontal_grid(
1911     image_desc_t
1912     *im)
1913 {
1914     double    range;
1915     double    scaledrange;
1916     int       pixel, i;
1917     int       gridind = 0;
1918     int       decimals, fractionals;
1919
1920     im->ygrid_scale.labfact = 2;
1921     range = im->maxval - im->minval;
1922     scaledrange = range / im->magfact;
1923     /* does the scale of this graph make it impossible to put lines
1924        on it? If so, give up. */
1925     if (isnan(scaledrange)) {
1926         return 0;
1927     }
1928
1929     /* find grid spaceing */
1930     pixel = 1;
1931     if (isnan(im->ygridstep)) {
1932         if (im->extra_flags & ALTYGRID) {
1933             /* find the value with max number of digits. Get number of digits */
1934             decimals =
1935                 ceil(log10
1936                      (max(fabs(im->maxval), fabs(im->minval)) *
1937                       im->viewfactor / im->magfact));
1938             if (decimals <= 0)  /* everything is small. make place for zero */
1939                 decimals = 1;
1940             im->ygrid_scale.gridstep =
1941                 pow((double) 10,
1942                     floor(log10(range * im->viewfactor / im->magfact))) /
1943                 im->viewfactor * im->magfact;
1944             if (im->ygrid_scale.gridstep == 0)  /* range is one -> 0.1 is reasonable scale */
1945                 im->ygrid_scale.gridstep = 0.1;
1946             /* should have at least 5 lines but no more then 15 */
1947             if (range / im->ygrid_scale.gridstep < 5
1948                 && im->ygrid_scale.gridstep >= 30)
1949                 im->ygrid_scale.gridstep /= 10;
1950             if (range / im->ygrid_scale.gridstep > 15)
1951                 im->ygrid_scale.gridstep *= 10;
1952             if (range / im->ygrid_scale.gridstep > 5) {
1953                 im->ygrid_scale.labfact = 1;
1954                 if (range / im->ygrid_scale.gridstep > 8
1955                     || im->ygrid_scale.gridstep <
1956                     1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1957                     im->ygrid_scale.labfact = 2;
1958             } else {
1959                 im->ygrid_scale.gridstep /= 5;
1960                 im->ygrid_scale.labfact = 5;
1961             }
1962             fractionals =
1963                 floor(log10
1964                       (im->ygrid_scale.gridstep *
1965                        (double) im->ygrid_scale.labfact * im->viewfactor /
1966                        im->magfact));
1967             if (fractionals < 0) {  /* small amplitude. */
1968                 int       len = decimals - fractionals + 1;
1969
1970                 if (im->unitslength < len + 2)
1971                     im->unitslength = len + 2;
1972                 sprintf(im->ygrid_scale.labfmt,
1973                         "%%%d.%df%s", len,
1974                         -fractionals, (im->symbol != ' ' ? " %c" : ""));
1975             } else {
1976                 int       len = decimals + 1;
1977
1978                 if (im->unitslength < len + 2)
1979                     im->unitslength = len + 2;
1980                 sprintf(im->ygrid_scale.labfmt,
1981                         "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1982             }
1983         } else {        /* classic rrd grid */
1984             for (i = 0; ylab[i].grid > 0; i++) {
1985                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1986                 gridind = i;
1987                 if (pixel >= 5)
1988                     break;
1989             }
1990
1991             for (i = 0; i < 4; i++) {
1992                 if (pixel * ylab[gridind].lfac[i] >=
1993                     1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1994                     im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1995                     break;
1996                 }
1997             }
1998
1999             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
2000         }
2001     } else {
2002         im->ygrid_scale.gridstep = im->ygridstep;
2003         im->ygrid_scale.labfact = im->ylabfact;
2004     }
2005     return 1;
2006 }
2007
2008 int draw_horizontal_grid(
2009     image_desc_t
2010     *im)
2011 {
2012     int       i;
2013     double    scaledstep;
2014     char      graph_label[100];
2015     int       nlabels = 0;
2016     double    X0 = im->xorigin;
2017     double    X1 = im->xorigin + im->xsize;
2018     int       sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2019     int       egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2020     double    MaxY;
2021     double second_axis_magfact = 0;
2022     char *second_axis_symb = "";
2023
2024     scaledstep =
2025         im->ygrid_scale.gridstep /
2026         (double) im->magfact * (double) im->viewfactor;
2027     MaxY = scaledstep * (double) egrid;
2028     for (i = sgrid; i <= egrid; i++) {
2029         double    Y0 = ytr(im,
2030                            im->ygrid_scale.gridstep * i);
2031         double    YN = ytr(im,
2032                            im->ygrid_scale.gridstep * (i + 1));
2033
2034         if (floor(Y0 + 0.5) >=
2035             im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2036             /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2037                with the chosen settings. Add a label if required by settings, or if
2038                there is only one label so far and the next grid line is out of bounds. */
2039             if (i % im->ygrid_scale.labfact == 0
2040                 || (nlabels == 1
2041                     && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2042                 if (im->symbol == ' ') {
2043                     if (im->extra_flags & ALTYGRID) {
2044                         sprintf(graph_label,
2045                                 im->ygrid_scale.labfmt,
2046                                 scaledstep * (double) i);
2047                     } else {
2048                         if (MaxY < 10) {
2049                             sprintf(graph_label, "%4.1f",
2050                                     scaledstep * (double) i);
2051                         } else {
2052                             sprintf(graph_label, "%4.0f",
2053                                     scaledstep * (double) i);
2054                         }
2055                     }
2056                 } else {
2057                     char      sisym = (i == 0 ? ' ' : im->symbol);
2058
2059                     if (im->extra_flags & ALTYGRID) {
2060                         sprintf(graph_label,
2061                                 im->ygrid_scale.labfmt,
2062                                 scaledstep * (double) i, sisym);
2063                     } else {
2064                         if (MaxY < 10) {
2065                             sprintf(graph_label, "%4.1f %c",
2066                                     scaledstep * (double) i, sisym);
2067                         } else {
2068                             sprintf(graph_label, "%4.0f %c",
2069                                     scaledstep * (double) i, sisym);
2070                         }
2071                     }
2072                 }
2073                 nlabels++;
2074                 if (im->second_axis_scale != 0){
2075                         char graph_label_right[100];
2076                         double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2077                         if (im->second_axis_format[0] == '\0'){
2078                             if (!second_axis_magfact){
2079                                 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2080                                 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2081                             }
2082                             sval /= second_axis_magfact;
2083
2084                             if(MaxY < 10) {
2085                                 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2086                             } else {
2087                                 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2088                             }
2089                         }
2090                         else {
2091                            sprintf(graph_label_right,im->second_axis_format,sval);
2092                         }
2093                         gfx_text ( im,
2094                                X1+7, Y0,
2095                                im->graph_col[GRC_FONT],
2096                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2097                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2098                                graph_label_right );
2099                 }
2100
2101                 gfx_text(im,
2102                          X0 -
2103                          im->
2104                          text_prop[TEXT_PROP_AXIS].
2105                          size, Y0,
2106                          im->graph_col[GRC_FONT],
2107                          im->
2108                          text_prop[TEXT_PROP_AXIS].
2109                          font_desc,
2110                          im->tabwidth, 0.0,
2111                          GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2112                 gfx_line(im, X0 - 2, Y0, X0, Y0,
2113                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2114                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2115                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2116                 gfx_dashed_line(im, X0 - 2, Y0,
2117                                 X1 + 2, Y0,
2118                                 MGRIDWIDTH,
2119                                 im->
2120                                 graph_col
2121                                 [GRC_MGRID],
2122                                 im->grid_dash_on, im->grid_dash_off);
2123             } else if (!(im->extra_flags & NOMINOR)) {
2124                 gfx_line(im,
2125                          X0 - 2, Y0,
2126                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2127                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2128                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2129                 gfx_dashed_line(im, X0 - 1, Y0,
2130                                 X1 + 1, Y0,
2131                                 GRIDWIDTH,
2132                                 im->
2133                                 graph_col[GRC_GRID],
2134                                 im->grid_dash_on, im->grid_dash_off);
2135             }
2136         }
2137     }
2138     return 1;
2139 }
2140
2141 /* this is frexp for base 10 */
2142 double    frexp10(
2143     double,
2144     double *);
2145 double frexp10(
2146     double x,
2147     double *e)
2148 {
2149     double    mnt;
2150     int       iexp;
2151
2152     iexp = floor(log((double)fabs(x)) / log((double)10));
2153     mnt = x / pow(10.0, iexp);
2154     if (mnt >= 10.0) {
2155         iexp++;
2156         mnt = x / pow(10.0, iexp);
2157     }
2158     *e = iexp;
2159     return mnt;
2160 }
2161
2162
2163 /* logaritmic horizontal grid */
2164 int horizontal_log_grid(
2165     image_desc_t
2166     *im)
2167 {
2168     double    yloglab[][10] = {
2169         {
2170          1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2171          0.0, 0.0, 0.0}, {
2172                           1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2173                           0.0, 0.0, 0.0}, {
2174                                            1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2175                                            0.0, 0.0, 0.0}, {
2176                                                             1.0, 2.0, 4.0,
2177                                                             6.0, 8.0, 10.,
2178                                                             0.0,
2179                                                             0.0, 0.0, 0.0}, {
2180                                                                              1.0,
2181                                                                              2.0,
2182                                                                              3.0,
2183                                                                              4.0,
2184                                                                              5.0,
2185                                                                              6.0,
2186                                                                              7.0,
2187                                                                              8.0,
2188                                                                              9.0,
2189                                                                              10.},
2190         {
2191          0, 0, 0, 0, 0, 0, 0, 0, 0, 0}  /* last line */
2192     };
2193     int       i, j, val_exp, min_exp;
2194     double    nex;      /* number of decades in data */
2195     double    logscale; /* scale in logarithmic space */
2196     int       exfrac = 1;   /* decade spacing */
2197     int       mid = -1; /* row in yloglab for major grid */
2198     double    mspac;    /* smallest major grid spacing (pixels) */
2199     int       flab;     /* first value in yloglab to use */
2200     double    value, tmp, pre_value;
2201     double    X0, X1, Y0;
2202     char      graph_label[100];
2203
2204     nex = log10(im->maxval / im->minval);
2205     logscale = im->ysize / nex;
2206     /* major spacing for data with high dynamic range */
2207     while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2208         if (exfrac == 1)
2209             exfrac = 3;
2210         else
2211             exfrac += 3;
2212     }
2213
2214     /* major spacing for less dynamic data */
2215     do {
2216         /* search best row in yloglab */
2217         mid++;
2218         for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2219         mspac = logscale * log10(10.0 / yloglab[mid][i]);
2220     }
2221     while (mspac >
2222            2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2223     if (mid)
2224         mid--;
2225     /* find first value in yloglab */
2226     for (flab = 0;
2227          yloglab[mid][flab] < 10
2228          && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2229     if (yloglab[mid][flab] == 10.0) {
2230         tmp += 1.0;
2231         flab = 0;
2232     }
2233     val_exp = tmp;
2234     if (val_exp % exfrac)
2235         val_exp += abs(-val_exp % exfrac);
2236     X0 = im->xorigin;
2237     X1 = im->xorigin + im->xsize;
2238     /* draw grid */
2239     pre_value = DNAN;
2240     while (1) {
2241
2242         value = yloglab[mid][flab] * pow(10.0, val_exp);
2243         if (AlmostEqual2sComplement(value, pre_value, 4))
2244             break;      /* it seems we are not converging */
2245         pre_value = value;
2246         Y0 = ytr(im, value);
2247         if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2248             break;
2249         /* major grid line */
2250         gfx_line(im,
2251                  X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2252         gfx_line(im, X1, Y0, X1 + 2, Y0,
2253                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2254         gfx_dashed_line(im, X0 - 2, Y0,
2255                         X1 + 2, Y0,
2256                         MGRIDWIDTH,
2257                         im->
2258                         graph_col
2259                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2260         /* label */
2261         if (im->extra_flags & FORCE_UNITS_SI) {
2262             int       scale;
2263             double    pvalue;
2264             char      symbol;
2265
2266             scale = floor(val_exp / 3.0);
2267             if (value >= 1.0)
2268                 pvalue = pow(10.0, val_exp % 3);
2269             else
2270                 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2271             pvalue *= yloglab[mid][flab];
2272             if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2273                 && ((scale + si_symbcenter) >= 0))
2274                 symbol = si_symbol[scale + si_symbcenter];
2275             else
2276                 symbol = '?';
2277             sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2278         } else {
2279             sprintf(graph_label, "%3.0e", value);
2280         }
2281         if (im->second_axis_scale != 0){
2282                 char graph_label_right[100];
2283                 double sval = value*im->second_axis_scale+im->second_axis_shift;
2284                 if (im->second_axis_format[0] == '\0'){
2285                         if (im->extra_flags & FORCE_UNITS_SI) {
2286                                 double mfac = 1;
2287                                 char   *symb = "";
2288                                 auto_scale(im,&sval,&symb,&mfac);
2289                                 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2290                         }
2291                         else {
2292                                 sprintf(graph_label_right,"%3.0e", sval);
2293                         }
2294                 }
2295                 else {
2296                       sprintf(graph_label_right,im->second_axis_format,sval);
2297                 }
2298
2299                 gfx_text ( im,
2300                                X1+7, Y0,
2301                                im->graph_col[GRC_FONT],
2302                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2303                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2304                                graph_label_right );
2305         }
2306
2307         gfx_text(im,
2308                  X0 -
2309                  im->
2310                  text_prop[TEXT_PROP_AXIS].
2311                  size, Y0,
2312                  im->graph_col[GRC_FONT],
2313                  im->
2314                  text_prop[TEXT_PROP_AXIS].
2315                  font_desc,
2316                  im->tabwidth, 0.0,
2317                  GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2318         /* minor grid */
2319         if (mid < 4 && exfrac == 1) {
2320             /* find first and last minor line behind current major line
2321              * i is the first line and j tha last */
2322             if (flab == 0) {
2323                 min_exp = val_exp - 1;
2324                 for (i = 1; yloglab[mid][i] < 10.0; i++);
2325                 i = yloglab[mid][i - 1] + 1;
2326                 j = 10;
2327             } else {
2328                 min_exp = val_exp;
2329                 i = yloglab[mid][flab - 1] + 1;
2330                 j = yloglab[mid][flab];
2331             }
2332
2333             /* draw minor lines below current major line */
2334             for (; i < j; i++) {
2335
2336                 value = i * pow(10.0, min_exp);
2337                 if (value < im->minval)
2338                     continue;
2339                 Y0 = ytr(im, value);
2340                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2341                     break;
2342                 /* draw lines */
2343                 gfx_line(im,
2344                          X0 - 2, Y0,
2345                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2346                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2347                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2348                 gfx_dashed_line(im, X0 - 1, Y0,
2349                                 X1 + 1, Y0,
2350                                 GRIDWIDTH,
2351                                 im->
2352                                 graph_col[GRC_GRID],
2353                                 im->grid_dash_on, im->grid_dash_off);
2354             }
2355         } else if (exfrac > 1) {
2356             for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2357                 value = pow(10.0, i);
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         }
2377
2378         /* next decade */
2379         if (yloglab[mid][++flab] == 10.0) {
2380             flab = 0;
2381             val_exp += exfrac;
2382         }
2383     }
2384
2385     /* draw minor lines after highest major line */
2386     if (mid < 4 && exfrac == 1) {
2387         /* find first and last minor line below current major line
2388          * i is the first line and j tha last */
2389         if (flab == 0) {
2390             min_exp = val_exp - 1;
2391             for (i = 1; yloglab[mid][i] < 10.0; i++);
2392             i = yloglab[mid][i - 1] + 1;
2393             j = 10;
2394         } else {
2395             min_exp = val_exp;
2396             i = yloglab[mid][flab - 1] + 1;
2397             j = yloglab[mid][flab];
2398         }
2399
2400         /* draw minor lines below current major line */
2401         for (; i < j; i++) {
2402
2403             value = i * pow(10.0, min_exp);
2404             if (value < im->minval)
2405                 continue;
2406             Y0 = ytr(im, value);
2407             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2408                 break;
2409             /* draw lines */
2410             gfx_line(im,
2411                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2412             gfx_line(im, X1, Y0, X1 + 2, Y0,
2413                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2414             gfx_dashed_line(im, X0 - 1, Y0,
2415                             X1 + 1, Y0,
2416                             GRIDWIDTH,
2417                             im->
2418                             graph_col[GRC_GRID],
2419                             im->grid_dash_on, im->grid_dash_off);
2420         }
2421     }
2422     /* fancy minor gridlines */
2423     else if (exfrac > 1) {
2424         for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2425             value = pow(10.0, i);
2426             if (value < im->minval)
2427                 continue;
2428             Y0 = ytr(im, value);
2429             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2430                 break;
2431             /* draw lines */
2432             gfx_line(im,
2433                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2434             gfx_line(im, X1, Y0, X1 + 2, Y0,
2435                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2436             gfx_dashed_line(im, X0 - 1, Y0,
2437                             X1 + 1, Y0,
2438                             GRIDWIDTH,
2439                             im->
2440                             graph_col[GRC_GRID],
2441                             im->grid_dash_on, im->grid_dash_off);
2442         }
2443     }
2444
2445     return 1;
2446 }
2447
2448
2449 void vertical_grid(
2450     image_desc_t *im)
2451 {
2452     int       xlab_sel; /* which sort of label and grid ? */
2453     time_t    ti, tilab, timajor;
2454     long      factor;
2455     char      graph_label[100];
2456     double    X0, Y0, Y1;   /* points for filled graph and more */
2457     struct tm tm;
2458
2459     /* the type of time grid is determined by finding
2460        the number of seconds per pixel in the graph */
2461     if (im->xlab_user.minsec == -1) {
2462         factor = (im->end - im->start) / im->xsize;
2463         xlab_sel = 0;
2464         while (xlab[xlab_sel + 1].minsec !=
2465                -1 && xlab[xlab_sel + 1].minsec <= factor) {
2466             xlab_sel++;
2467         }               /* pick the last one */
2468         while (xlab[xlab_sel - 1].minsec ==
2469                xlab[xlab_sel].minsec
2470                && xlab[xlab_sel].length > (im->end - im->start)) {
2471             xlab_sel--;
2472         }               /* go back to the smallest size */
2473         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2474         im->xlab_user.gridst = xlab[xlab_sel].gridst;
2475         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2476         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2477         im->xlab_user.labtm = xlab[xlab_sel].labtm;
2478         im->xlab_user.labst = xlab[xlab_sel].labst;
2479         im->xlab_user.precis = xlab[xlab_sel].precis;
2480         im->xlab_user.stst = xlab[xlab_sel].stst;
2481     }
2482
2483     /* y coords are the same for every line ... */
2484     Y0 = im->yorigin;
2485     Y1 = im->yorigin - im->ysize;
2486     /* paint the minor grid */
2487     if (!(im->extra_flags & NOMINOR)) {
2488         for (ti = find_first_time(im->start,
2489                                   im->
2490                                   xlab_user.
2491                                   gridtm,
2492                                   im->
2493                                   xlab_user.
2494                                   gridst),
2495              timajor =
2496              find_first_time(im->start,
2497                              im->xlab_user.
2498                              mgridtm,
2499                              im->xlab_user.
2500                              mgridst);
2501              ti < im->end;
2502              ti =
2503              find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2504             ) {
2505             /* are we inside the graph ? */
2506             if (ti < im->start || ti > im->end)
2507                 continue;
2508             while (timajor < ti) {
2509                 timajor = find_next_time(timajor,
2510                                          im->
2511                                          xlab_user.
2512                                          mgridtm, im->xlab_user.mgridst);
2513             }
2514             if (ti == timajor)
2515                 continue;   /* skip as falls on major grid line */
2516             X0 = xtr(im, ti);
2517             gfx_line(im, X0, Y1 - 2, X0, Y1,
2518                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2519             gfx_line(im, X0, Y0, X0, Y0 + 2,
2520                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2521             gfx_dashed_line(im, X0, Y0 + 1, X0,
2522                             Y1 - 1, GRIDWIDTH,
2523                             im->
2524                             graph_col[GRC_GRID],
2525                             im->grid_dash_on, im->grid_dash_off);
2526         }
2527     }
2528
2529     /* paint the major grid */
2530     for (ti = find_first_time(im->start,
2531                               im->
2532                               xlab_user.
2533                               mgridtm,
2534                               im->
2535                               xlab_user.
2536                               mgridst);
2537          ti < im->end;
2538          ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2539         ) {
2540         /* are we inside the graph ? */
2541         if (ti < im->start || ti > im->end)
2542             continue;
2543         X0 = xtr(im, ti);
2544         gfx_line(im, X0, Y1 - 2, X0, Y1,
2545                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2546         gfx_line(im, X0, Y0, X0, Y0 + 3,
2547                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2548         gfx_dashed_line(im, X0, Y0 + 3, X0,
2549                         Y1 - 2, MGRIDWIDTH,
2550                         im->
2551                         graph_col
2552                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2553     }
2554     /* paint the labels below the graph */
2555     for (ti =
2556          find_first_time(im->start -
2557                          im->xlab_user.
2558                          precis / 2,
2559                          im->xlab_user.
2560                          labtm,
2561                          im->xlab_user.
2562                          labst);
2563          ti <=
2564          im->end -
2565          im->xlab_user.precis / 2;
2566          ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2567         ) {
2568         tilab = ti + im->xlab_user.precis / 2;  /* correct time for the label */
2569         /* are we inside the graph ? */
2570         if (tilab < im->start || tilab > im->end)
2571             continue;
2572 #if HAVE_STRFTIME
2573         localtime_r(&tilab, &tm);
2574         strftime(graph_label, 99, im->xlab_user.stst, &tm);
2575 #else
2576 # error "your libc has no strftime I guess we'll abort the exercise here."
2577 #endif
2578         gfx_text(im,
2579                  xtr(im, tilab),
2580                  Y0 + 3,
2581                  im->graph_col[GRC_FONT],
2582                  im->
2583                  text_prop[TEXT_PROP_AXIS].
2584                  font_desc,
2585                  im->tabwidth, 0.0,
2586                  GFX_H_CENTER, GFX_V_TOP, graph_label);
2587     }
2588
2589 }
2590
2591
2592 void axis_paint(
2593     image_desc_t *im)
2594 {
2595     /* draw x and y axis */
2596     /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2597        im->xorigin+im->xsize,im->yorigin-im->ysize,
2598        GRIDWIDTH, im->graph_col[GRC_AXIS]);
2599
2600        gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2601        im->xorigin+im->xsize,im->yorigin-im->ysize,
2602        GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2603
2604     gfx_line(im, im->xorigin - 4,
2605              im->yorigin,
2606              im->xorigin + im->xsize +
2607              4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2608     gfx_line(im, im->xorigin,
2609              im->yorigin + 4,
2610              im->xorigin,
2611              im->yorigin - im->ysize -
2612              4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2613     /* arrow for X and Y axis direction */
2614     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 */
2615                  im->graph_col[GRC_ARROW]);
2616     gfx_close_path(im);
2617     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 */
2618                  im->graph_col[GRC_ARROW]);
2619     gfx_close_path(im);
2620     if (im->second_axis_scale != 0){
2621        gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2622                          im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2623                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2624        gfx_new_area ( im,
2625                    im->xorigin+im->xsize-2,  im->yorigin-im->ysize-2,
2626                    im->xorigin+im->xsize+3,  im->yorigin-im->ysize-2,
2627                    im->xorigin+im->xsize,    im->yorigin-im->ysize-7, /* LINEOFFSET */
2628                    im->graph_col[GRC_ARROW]);
2629        gfx_close_path(im);
2630     }
2631
2632 }
2633
2634 void grid_paint(
2635     image_desc_t *im)
2636 {
2637     long      i;
2638     int       res = 0;
2639     double    X0, Y0;   /* points for filled graph and more */
2640     struct gfx_color_t water_color;
2641
2642     if (im->draw_3d_border > 0) {
2643             /* draw 3d border */
2644             i = im->draw_3d_border;
2645             gfx_new_area(im, 0, im->yimg,
2646                          i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2647             gfx_add_point(im, im->ximg - i, i);
2648             gfx_add_point(im, im->ximg, 0);
2649             gfx_add_point(im, 0, 0);
2650             gfx_close_path(im);
2651             gfx_new_area(im, i, im->yimg - i,
2652                          im->ximg - i,
2653                          im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2654             gfx_add_point(im, im->ximg, 0);
2655             gfx_add_point(im, im->ximg, im->yimg);
2656             gfx_add_point(im, 0, im->yimg);
2657             gfx_close_path(im);
2658     }
2659     if (im->draw_x_grid == 1)
2660         vertical_grid(im);
2661     if (im->draw_y_grid == 1) {
2662         if (im->logarithmic) {
2663             res = horizontal_log_grid(im);
2664         } else {
2665             res = draw_horizontal_grid(im);
2666         }
2667
2668         /* dont draw horizontal grid if there is no min and max val */
2669         if (!res) {
2670             char     *nodata = "No Data found";
2671
2672             gfx_text(im, im->ximg / 2,
2673                      (2 * im->yorigin -
2674                       im->ysize) / 2,
2675                      im->graph_col[GRC_FONT],
2676                      im->
2677                      text_prop[TEXT_PROP_AXIS].
2678                      font_desc,
2679                      im->tabwidth, 0.0,
2680                      GFX_H_CENTER, GFX_V_CENTER, nodata);
2681         }
2682     }
2683
2684     /* yaxis unit description */
2685     if (im->ylegend[0] != '\0'){
2686         gfx_text(im,
2687                  im->xOriginLegendY+10,
2688                  im->yOriginLegendY,
2689                  im->graph_col[GRC_FONT],
2690                  im->
2691                  text_prop[TEXT_PROP_UNIT].
2692                  font_desc,
2693                  im->tabwidth,
2694                  RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2695
2696     }
2697     if (im->second_axis_legend[0] != '\0'){
2698             gfx_text( im,
2699                   im->xOriginLegendY2+10,
2700                   im->yOriginLegendY2,
2701                   im->graph_col[GRC_FONT],
2702                   im->text_prop[TEXT_PROP_UNIT].font_desc,
2703                   im->tabwidth,
2704                   RRDGRAPH_YLEGEND_ANGLE,
2705                   GFX_H_CENTER, GFX_V_CENTER,
2706                   im->second_axis_legend);
2707     }
2708
2709     /* graph title */
2710     gfx_text(im,
2711              im->xOriginTitle, im->yOriginTitle+6,
2712              im->graph_col[GRC_FONT],
2713              im->
2714              text_prop[TEXT_PROP_TITLE].
2715              font_desc,
2716              im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2717     /* rrdtool 'logo' */
2718     if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2719         water_color = im->graph_col[GRC_FONT];
2720         water_color.alpha = 0.3;
2721         double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2722         gfx_text(im, xpos, 5,
2723                  water_color,
2724                  im->
2725                  text_prop[TEXT_PROP_WATERMARK].
2726                  font_desc, im->tabwidth,
2727                  -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2728     }
2729     /* graph watermark */
2730     if (im->watermark[0] != '\0') {
2731         water_color = im->graph_col[GRC_FONT];
2732         water_color.alpha = 0.3;
2733         gfx_text(im,
2734                  im->ximg / 2, im->yimg - 6,
2735                  water_color,
2736                  im->
2737                  text_prop[TEXT_PROP_WATERMARK].
2738                  font_desc, im->tabwidth, 0,
2739                  GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2740     }
2741
2742     /* graph labels */
2743     if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2744         for (i = 0; i < im->gdes_c; i++) {
2745             if (im->gdes[i].legend[0] == '\0')
2746                 continue;
2747             /* im->gdes[i].leg_y is the bottom of the legend */
2748             X0 = im->xOriginLegend + im->gdes[i].leg_x;
2749             Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2750             gfx_text(im, X0, Y0,
2751                      im->graph_col[GRC_FONT],
2752                      im->
2753                      text_prop
2754                      [TEXT_PROP_LEGEND].font_desc,
2755                      im->tabwidth, 0.0,
2756                      GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2757             /* The legend for GRAPH items starts with "M " to have
2758                enough space for the box */
2759             if (im->gdes[i].gf != GF_PRINT &&
2760                 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2761                 double    boxH, boxV;
2762                 double    X1, Y1;
2763
2764                 boxH = gfx_get_text_width(im, 0,
2765                                           im->
2766                                           text_prop
2767                                           [TEXT_PROP_LEGEND].
2768                                           font_desc,
2769                                           im->tabwidth, "o") * 1.2;
2770                 boxV = boxH;
2771                 /* shift the box up a bit */
2772                 Y0 -= boxV * 0.4;
2773
2774         if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */ 
2775                         cairo_save(im->cr);
2776                         cairo_new_path(im->cr);
2777                         cairo_set_line_width(im->cr, 1.0);
2778                         gfx_line(im,
2779                                 X0, Y0 - boxV / 2,
2780                                 X0 + boxH, Y0 - boxV / 2,
2781                                 1.0, im->gdes[i].col);
2782                         gfx_close_path(im);
2783                 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2784                         cairo_save(im->cr);
2785                         cairo_new_path(im->cr);
2786                         cairo_set_line_width(im->cr, 1.0);
2787                         gfx_line(im,
2788                                 X0 + boxH / 2, Y0,
2789                                 X0 + boxH / 2, Y0 - boxV,
2790                                 1.0, im->gdes[i].col);
2791                         gfx_close_path(im);
2792                 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2793                         cairo_save(im->cr);
2794                         cairo_new_path(im->cr);
2795                         cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2796                         gfx_line(im,
2797                                 X0, Y0,
2798                                 X0 + boxH, Y0 - boxV,
2799                                 im->gdes[i].linewidth, im->gdes[i].col);
2800                         gfx_close_path(im);
2801                 } else {
2802                 /* make sure transparent colors show up the same way as in the graph */
2803                         gfx_new_area(im,
2804                                      X0, Y0 - boxV,
2805                                      X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2806                         gfx_add_point(im, X0 + boxH, Y0 - boxV);
2807                         gfx_close_path(im);
2808                         gfx_new_area(im, X0, Y0 - boxV, X0,
2809                                      Y0, X0 + boxH, Y0, im->gdes[i].col);
2810                         gfx_add_point(im, X0 + boxH, Y0 - boxV);
2811                         gfx_close_path(im);
2812                         cairo_save(im->cr);
2813                         cairo_new_path(im->cr);
2814                         cairo_set_line_width(im->cr, 1.0);
2815                         X1 = X0 + boxH;
2816                         Y1 = Y0 - boxV;
2817                         gfx_line_fit(im, &X0, &Y0);
2818                         gfx_line_fit(im, &X1, &Y1);
2819                         cairo_move_to(im->cr, X0, Y0);
2820                         cairo_line_to(im->cr, X1, Y0);
2821                         cairo_line_to(im->cr, X1, Y1);
2822                         cairo_line_to(im->cr, X0, Y1);
2823                         cairo_close_path(im->cr);
2824                         cairo_set_source_rgba(im->cr,
2825                                               im->graph_col[GRC_FRAME].red,
2826                                               im->graph_col[GRC_FRAME].green,
2827                                               im->graph_col[GRC_FRAME].blue,
2828                                               im->graph_col[GRC_FRAME].alpha);
2829                 }
2830                 if (im->gdes[i].dash) {
2831                     /* make box borders in legend dashed if the graph is dashed */
2832                     double    dashes[] = {
2833                         3.0
2834                     };
2835                     cairo_set_dash(im->cr, dashes, 1, 0.0);
2836                 }
2837                 cairo_stroke(im->cr);
2838                 cairo_restore(im->cr);
2839             }
2840         }
2841     }
2842 }
2843
2844
2845 /*****************************************************
2846  * lazy check make sure we rely need to create this graph
2847  *****************************************************/
2848
2849 int lazy_check(
2850     image_desc_t *im)
2851 {
2852     FILE     *fd = NULL;
2853     int       size = 1;
2854     struct stat imgstat;
2855
2856     if (im->lazy == 0)
2857         return 0;       /* no lazy option */
2858     if (strlen(im->graphfile) == 0)
2859         return 0;       /* inmemory option */
2860     if (stat(im->graphfile, &imgstat) != 0)
2861         return 0;       /* can't stat */
2862     /* one pixel in the existing graph is more then what we would
2863        change here ... */
2864     if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2865         return 0;
2866     if ((fd = fopen(im->graphfile, "rb")) == NULL)
2867         return 0;       /* the file does not exist */
2868     switch (im->imgformat) {
2869     case IF_PNG:
2870         size = PngSize(fd, &(im->ximg), &(im->yimg));
2871         break;
2872     default:
2873         size = 1;
2874     }
2875     fclose(fd);
2876     return size;
2877 }
2878
2879
2880 int graph_size_location(
2881     image_desc_t
2882     *im,
2883     int elements)
2884 {
2885     /* The actual size of the image to draw is determined from
2886      ** several sources.  The size given on the command line is
2887      ** the graph area but we need more as we have to draw labels
2888      ** and other things outside the graph area. If the option
2889      ** --full-size-mode is selected the size defines the total
2890      ** image size and the size available for the graph is
2891      ** calculated.
2892      */
2893
2894     /** +---+-----------------------------------+
2895      ** | y |...............graph title.........|
2896      ** |   +---+-------------------------------+
2897      ** | a | y |                               |
2898      ** | x |   |                               |
2899      ** | i | a |                               |
2900      ** | s | x |       main graph area         |
2901      ** |   | i |                               |
2902      ** | t | s |                               |
2903      ** | i |   |                               |
2904      ** | t | l |                               |
2905      ** | l | b +-------------------------------+
2906      ** | e | l |       x axis labels           |
2907      ** +---+---+-------------------------------+
2908      ** |....................legends............|
2909      ** +---------------------------------------+
2910      ** |                   watermark           |
2911      ** +---------------------------------------+
2912      */
2913
2914     int       Xvertical = 0, Xvertical2 = 0, Ytitle =
2915         0, Xylabel = 0, Xmain = 0, Ymain =
2916         0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2917
2918     // no legends and no the shall be plotted it's easy
2919     if (im->extra_flags & ONLY_GRAPH) {
2920         im->xorigin = 0;
2921         im->ximg = im->xsize;
2922         im->yimg = im->ysize;
2923         im->yorigin = im->ysize;
2924         ytr(im, DNAN);
2925         return 0;
2926     }
2927
2928     if(im->watermark[0] != '\0') {
2929         Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2930     }
2931
2932     // calculate the width of the left vertical legend
2933     if (im->ylegend[0] != '\0') {
2934         Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2935     }
2936
2937     // calculate the width of the right vertical legend
2938     if (im->second_axis_legend[0] != '\0') {
2939         Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2940     }
2941     else{
2942         Xvertical2 = Xspacing;
2943     }
2944
2945     if (im->title[0] != '\0') {
2946         /* The title is placed "inbetween" two text lines so it
2947          ** automatically has some vertical spacing.  The horizontal
2948          ** spacing is added here, on each side.
2949          */
2950         /* if necessary, reduce the font size of the title until it fits the image width */
2951         Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2952     }
2953     else{
2954         // we have no title; get a little clearing from the top
2955         Ytitle = 1.5 * Yspacing;
2956     }
2957
2958     if (elements) {
2959         if (im->draw_x_grid) {
2960             // calculate the height of the horizontal labelling
2961             Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2962         }
2963         if (im->draw_y_grid || im->forceleftspace) {
2964             // calculate the width of the vertical labelling
2965             Xylabel =
2966                 gfx_get_text_width(im, 0,
2967                                    im->text_prop[TEXT_PROP_AXIS].font_desc,
2968                                    im->tabwidth, "0") * im->unitslength;
2969         }
2970     }
2971
2972     // add some space to the labelling
2973     Xylabel += Xspacing;
2974
2975     /* If the legend is printed besides the graph the width has to be
2976      ** calculated first. Placing the legend north or south of the
2977      ** graph requires the width calculation first, so the legend is
2978      ** skipped for the moment.
2979      */
2980     im->legendheight = 0;
2981     im->legendwidth = 0;
2982     if (!(im->extra_flags & NOLEGEND)) {
2983         if(im->legendposition == WEST || im->legendposition == EAST){
2984             if (leg_place(im, 1) == -1){
2985                 return -1;
2986             }
2987         }
2988     }
2989
2990     if (im->extra_flags & FULL_SIZE_MODE) {
2991
2992         /* The actual size of the image to draw has been determined by the user.
2993          ** The graph area is the space remaining after accounting for the legend,
2994          ** the watermark, the axis labels, and the title.
2995          */
2996         im->ximg = im->xsize;
2997         im->yimg = im->ysize;
2998         Xmain = im->ximg;
2999         Ymain = im->yimg;
3000
3001         /* Now calculate the total size.  Insert some spacing where
3002            desired.  im->xorigin and im->yorigin need to correspond
3003            with the lower left corner of the main graph area or, if
3004            this one is not set, the imaginary box surrounding the
3005            pie chart area. */
3006         /* Initial size calculation for the main graph area */
3007
3008         Xmain -= Xylabel;// + Xspacing;
3009         if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3010             Xmain -= im->legendwidth;// + Xspacing;
3011         }
3012         if (im->second_axis_scale != 0){
3013             Xmain -= Xylabel;
3014         }
3015         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3016             Xmain -= Xspacing;
3017         }
3018
3019         Xmain -= Xvertical + Xvertical2;
3020
3021         /* limit the remaining space to 0 */
3022         if(Xmain < 1){
3023             Xmain = 1;
3024         }
3025         im->xsize = Xmain;
3026
3027         /* Putting the legend north or south, the height can now be calculated */
3028         if (!(im->extra_flags & NOLEGEND)) {
3029             if(im->legendposition == NORTH || im->legendposition == SOUTH){
3030                 im->legendwidth = im->ximg;
3031                 if (leg_place(im, 0) == -1){
3032                     return -1;
3033                 }
3034             }
3035         }
3036
3037         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3038             Ymain -=  Yxlabel + im->legendheight;
3039         }
3040         else{
3041             Ymain -= Yxlabel;
3042         }
3043
3044         /* reserve space for the title *or* some padding above the graph */
3045         Ymain -= Ytitle;
3046
3047             /* reserve space for padding below the graph */
3048         if (im->extra_flags & NOLEGEND) {
3049             Ymain -= Yspacing;
3050         }
3051
3052         if (im->watermark[0] != '\0') {
3053             Ymain -= Ywatermark;
3054         }
3055         /* limit the remaining height to 0 */
3056         if(Ymain < 1){
3057             Ymain = 1;
3058         }
3059         im->ysize = Ymain;
3060     } else {            /* dimension options -width and -height refer to the dimensions of the main graph area */
3061
3062         /* The actual size of the image to draw is determined from
3063          ** several sources.  The size given on the command line is
3064          ** the graph area but we need more as we have to draw labels
3065          ** and other things outside the graph area.
3066          */
3067
3068         if (elements) {
3069             Xmain = im->xsize; // + Xspacing;
3070             Ymain = im->ysize;
3071         }
3072
3073         im->ximg = Xmain + Xylabel;
3074         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3075             im->ximg += Xspacing;
3076         }
3077
3078         if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3079             im->ximg += im->legendwidth;// + Xspacing;
3080         }
3081         if (im->second_axis_scale != 0){
3082             im->ximg += Xylabel;
3083         }
3084
3085         im->ximg += Xvertical + Xvertical2;
3086
3087         if (!(im->extra_flags & NOLEGEND)) {
3088             if(im->legendposition == NORTH || im->legendposition == SOUTH){
3089                 im->legendwidth = im->ximg;
3090                 if (leg_place(im, 0) == -1){
3091                     return -1;
3092                 }
3093             }
3094         }
3095
3096         im->yimg = Ymain + Yxlabel;
3097         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3098              im->yimg += im->legendheight;
3099         }
3100
3101         /* reserve space for the title *or* some padding above the graph */
3102         if (Ytitle) {
3103             im->yimg += Ytitle;
3104         } else {
3105             im->yimg += 1.5 * Yspacing;
3106         }
3107         /* reserve space for padding below the graph */
3108         if (im->extra_flags & NOLEGEND) {
3109             im->yimg += Yspacing;
3110         }
3111
3112         if (im->watermark[0] != '\0') {
3113             im->yimg += Ywatermark;
3114         }
3115     }
3116
3117
3118     /* In case of putting the legend in west or east position the first
3119      ** legend calculation might lead to wrong positions if some items
3120      ** are not aligned on the left hand side (e.g. centered) as the
3121      ** legendwidth wight have been increased after the item was placed.
3122      ** In this case the positions have to be recalculated.
3123      */
3124     if (!(im->extra_flags & NOLEGEND)) {
3125         if(im->legendposition == WEST || im->legendposition == EAST){
3126             if (leg_place(im, 0) == -1){
3127                 return -1;
3128             }
3129         }
3130     }
3131
3132     /* After calculating all dimensions
3133      ** it is now possible to calculate
3134      ** all offsets.
3135      */
3136     switch(im->legendposition){
3137         case NORTH:
3138             im->xOriginTitle   = Xvertical + Xylabel + (im->xsize / 2);
3139             im->yOriginTitle   = 0;
3140
3141             im->xOriginLegend  = 0;
3142             im->yOriginLegend  = Ytitle;
3143
3144             im->xOriginLegendY = 0;
3145             im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3146
3147             im->xorigin        = Xvertical + Xylabel;
3148             im->yorigin        = Ytitle + im->legendheight + Ymain;
3149
3150             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3151             if (im->second_axis_scale != 0){
3152                 im->xOriginLegendY2 += Xylabel;
3153             }
3154             im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3155
3156             break;
3157
3158         case WEST:
3159             im->xOriginTitle   = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3160             im->yOriginTitle   = 0;
3161
3162             im->xOriginLegend  = 0;
3163             im->yOriginLegend  = Ytitle;
3164
3165             im->xOriginLegendY = im->legendwidth;
3166             im->yOriginLegendY = Ytitle + (Ymain / 2);
3167
3168             im->xorigin        = im->legendwidth + Xvertical + Xylabel;
3169             im->yorigin        = Ytitle + Ymain;
3170
3171             im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3172             if (im->second_axis_scale != 0){
3173                 im->xOriginLegendY2 += Xylabel;
3174             }
3175             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3176
3177             break;
3178
3179         case SOUTH:
3180             im->xOriginTitle   = Xvertical + Xylabel + im->xsize / 2;
3181             im->yOriginTitle   = 0;
3182
3183             im->xOriginLegend  = 0;
3184             im->yOriginLegend  = Ytitle + Ymain + Yxlabel;
3185
3186             im->xOriginLegendY = 0;
3187             im->yOriginLegendY = Ytitle + (Ymain / 2);
3188
3189             im->xorigin        = Xvertical + Xylabel;
3190             im->yorigin        = Ytitle + Ymain;
3191
3192             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3193             if (im->second_axis_scale != 0){
3194                 im->xOriginLegendY2 += Xylabel;
3195             }
3196             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3197
3198             break;
3199
3200         case EAST:
3201             im->xOriginTitle   = Xvertical + Xylabel + im->xsize / 2;
3202             im->yOriginTitle   = 0;
3203
3204             im->xOriginLegend  = Xvertical + Xylabel + Xmain + Xvertical2;
3205             if (im->second_axis_scale != 0){
3206                 im->xOriginLegend += Xylabel;
3207             }
3208             im->yOriginLegend  = Ytitle;
3209
3210             im->xOriginLegendY = 0;
3211             im->yOriginLegendY = Ytitle + (Ymain / 2);
3212
3213             im->xorigin        = Xvertical + Xylabel;
3214             im->yorigin        = Ytitle + Ymain;
3215
3216             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3217             if (im->second_axis_scale != 0){
3218                 im->xOriginLegendY2 += Xylabel;
3219             }
3220             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3221
3222             if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3223                 im->xOriginTitle    += Xspacing;
3224                 im->xOriginLegend   += Xspacing;
3225                 im->xOriginLegendY  += Xspacing;
3226                 im->xorigin         += Xspacing;
3227                 im->xOriginLegendY2 += Xspacing;
3228             }
3229             break;
3230     }
3231
3232     xtr(im, 0);
3233     ytr(im, DNAN);
3234     return 0;
3235 }
3236
3237 static cairo_status_t cairo_output(
3238     void *closure,
3239     const unsigned char
3240     *data,
3241     unsigned int length)
3242 {
3243     image_desc_t *im = (image_desc_t*)closure;
3244
3245     im->rendered_image =
3246         (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3247     if (im->rendered_image == NULL)
3248         return CAIRO_STATUS_WRITE_ERROR;
3249     memcpy(im->rendered_image + im->rendered_image_size, data, length);
3250     im->rendered_image_size += length;
3251     return CAIRO_STATUS_SUCCESS;
3252 }
3253
3254 /* draw that picture thing ... */
3255 int graph_paint(
3256     image_desc_t *im)
3257 {
3258     int       i, ii;
3259     int       lazy = lazy_check(im);
3260     double    areazero = 0.0;
3261     graph_desc_t *lastgdes = NULL;
3262     rrd_infoval_t info;
3263
3264 //    PangoFontMap *font_map = pango_cairo_font_map_get_default();
3265
3266     /* pull the data from the rrd files ... */
3267     if (data_fetch(im) == -1)
3268         return -1;
3269     /* evaluate VDEF and CDEF operations ... */
3270     if (data_calc(im) == -1)
3271         return -1;
3272     /* calculate and PRINT and GPRINT definitions. We have to do it at
3273      * this point because it will affect the length of the legends
3274      * if there are no graph elements (i==0) we stop here ...
3275      * if we are lazy, try to quit ...
3276      */
3277     i = print_calc(im);
3278     if (i < 0)
3279         return -1;
3280
3281     /* if we want and can be lazy ... quit now */
3282     if (i == 0)
3283         return 0;
3284
3285 /**************************************************************
3286  *** Calculating sizes and locations became a bit confusing ***
3287  *** so I moved this into a separate function.              ***
3288  **************************************************************/
3289     if (graph_size_location(im, i) == -1)
3290         return -1;
3291
3292     info.u_cnt = im->xorigin;
3293     grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3294     info.u_cnt = im->yorigin - im->ysize;
3295     grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3296     info.u_cnt = im->xsize;
3297     grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3298     info.u_cnt = im->ysize;
3299     grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3300     info.u_cnt = im->ximg;
3301     grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3302     info.u_cnt = im->yimg;
3303     grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3304     info.u_cnt = im->start;
3305     grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3306     info.u_cnt = im->end;
3307     grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3308
3309     /* if we want and can be lazy ... quit now */
3310     if (lazy)
3311         return 0;
3312
3313     /* get actual drawing data and find min and max values */
3314     if (data_proc(im) == -1)
3315         return -1;
3316     if (!im->logarithmic) {
3317         si_unit(im);
3318     }
3319
3320     /* identify si magnitude Kilo, Mega Giga ? */
3321     if (!im->rigid && !im->logarithmic)
3322         expand_range(im);   /* make sure the upper and lower limit are
3323                                sensible values */
3324
3325     info.u_val = im->minval;
3326     grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3327     info.u_val = im->maxval;
3328     grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3329
3330
3331     if (!calc_horizontal_grid(im))
3332         return -1;
3333     /* reset precalc */
3334     ytr(im, DNAN);
3335 /*   if (im->gridfit)
3336      apply_gridfit(im); */
3337     /* the actual graph is created by going through the individual
3338        graph elements and then drawing them */
3339     cairo_surface_destroy(im->surface);
3340     switch (im->imgformat) {
3341     case IF_PNG:
3342         im->surface =
3343             cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3344                                        im->ximg * im->zoom,
3345                                        im->yimg * im->zoom);
3346         break;
3347     case IF_PDF:
3348         im->gridfit = 0;
3349         im->surface = strlen(im->graphfile)
3350             ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3351                                        im->yimg * im->zoom)
3352             : cairo_pdf_surface_create_for_stream
3353             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3354         break;
3355     case IF_EPS:
3356         im->gridfit = 0;
3357         im->surface = strlen(im->graphfile)
3358             ?
3359             cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3360                                     im->yimg * im->zoom)
3361             : cairo_ps_surface_create_for_stream
3362             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3363         break;
3364     case IF_SVG:
3365         im->gridfit = 0;
3366         im->surface = strlen(im->graphfile)
3367             ?
3368             cairo_svg_surface_create(im->
3369                                      graphfile,
3370                                      im->ximg * im->zoom, im->yimg * im->zoom)
3371             : cairo_svg_surface_create_for_stream
3372             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3373         cairo_svg_surface_restrict_to_version
3374             (im->surface, CAIRO_SVG_VERSION_1_1);
3375         break;
3376     };
3377     cairo_destroy(im->cr);
3378     im->cr = cairo_create(im->surface);
3379     cairo_set_antialias(im->cr, im->graph_antialias);
3380     cairo_scale(im->cr, im->zoom, im->zoom);
3381 //    pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3382     gfx_new_area(im, 0, 0, 0, im->yimg,
3383                  im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3384     gfx_add_point(im, im->ximg, 0);
3385     gfx_close_path(im);
3386     gfx_new_area(im, im->xorigin,
3387                  im->yorigin,
3388                  im->xorigin +
3389                  im->xsize, im->yorigin,
3390                  im->xorigin +
3391                  im->xsize,
3392                  im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3393     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3394     gfx_close_path(im);
3395     cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3396                     im->xsize, im->ysize + 2.0);
3397     cairo_clip(im->