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