390a66ec162ebe8ee80c48cf3932a62bf5eab7ed
[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
1363 static int find_first_weekday(void){
1364     static int first_weekday = -1;
1365     if (first_weekday == -1){
1366 #if defined(HAVE_NL_LANGINFO)
1367         /* according to http://sourceware.org/ml/libc-locales/2009-q1/msg00011.html */
1368         long week_1stday_l = (long) nl_langinfo (_NL_TIME_WEEK_1STDAY);
1369         if (week_1stday_l == 19971130) first_weekday = 0; /* Sun */
1370         else if (week_1stday_l == 19971201) first_weekday = 1; /* Mon */
1371         else first_weekday = 1; /* we go for a monday default */
1372 #else
1373         first_weekday = 1;
1374 #endif
1375     }
1376     return first_weekday;
1377 }
1378
1379 /* identify the point where the first gridline, label ... gets placed */
1380
1381 time_t find_first_time(
1382     time_t start,       /* what is the initial time */
1383     enum tmt_en baseint,    /* what is the basic interval */
1384     long basestep       /* how many if these do we jump a time */
1385     )
1386 {
1387     struct tm tm;
1388
1389     localtime_r(&start, &tm);
1390
1391     switch (baseint) {
1392     case TMT_SECOND:
1393         tm.       tm_sec -= tm.tm_sec % basestep;
1394
1395         break;
1396     case TMT_MINUTE:
1397         tm.       tm_sec = 0;
1398         tm.       tm_min -= tm.tm_min % basestep;
1399
1400         break;
1401     case TMT_HOUR:
1402         tm.       tm_sec = 0;
1403         tm.       tm_min = 0;
1404         tm.       tm_hour -= tm.tm_hour % basestep;
1405
1406         break;
1407     case TMT_DAY:
1408         /* we do NOT look at the basestep for this ... */
1409         tm.       tm_sec = 0;
1410         tm.       tm_min = 0;
1411         tm.       tm_hour = 0;
1412
1413         break;
1414     case TMT_WEEK:
1415         /* we do NOT look at the basestep for this ... */
1416         tm.       tm_sec = 0;
1417         tm.       tm_min = 0;
1418         tm.       tm_hour = 0;
1419         tm.       tm_mday -= tm.tm_wday - find_first_weekday();
1420
1421         if (tm.tm_wday == 0 && find_first_weekday() > 0)
1422             tm.       tm_mday -= 7; /* we want the *previous* week */
1423
1424         break;
1425     case TMT_MONTH:
1426         tm.       tm_sec = 0;
1427         tm.       tm_min = 0;
1428         tm.       tm_hour = 0;
1429         tm.       tm_mday = 1;
1430         tm.       tm_mon -= tm.tm_mon % basestep;
1431
1432         break;
1433
1434     case TMT_YEAR:
1435         tm.       tm_sec = 0;
1436         tm.       tm_min = 0;
1437         tm.       tm_hour = 0;
1438         tm.       tm_mday = 1;
1439         tm.       tm_mon = 0;
1440         tm.       tm_year -= (
1441     tm.tm_year + 1900) %basestep;
1442
1443     }
1444     return mktime(&tm);
1445 }
1446
1447 /* identify the point where the next gridline, label ... gets placed */
1448 time_t find_next_time(
1449     time_t current,     /* what is the initial time */
1450     enum tmt_en baseint,    /* what is the basic interval */
1451     long basestep       /* how many if these do we jump a time */
1452     )
1453 {
1454     struct tm tm;
1455     time_t    madetime;
1456
1457     localtime_r(&current, &tm);
1458
1459     do {
1460         switch (baseint) {
1461         case TMT_SECOND:
1462             tm.       tm_sec += basestep;
1463
1464             break;
1465         case TMT_MINUTE:
1466             tm.       tm_min += basestep;
1467
1468             break;
1469         case TMT_HOUR:
1470             tm.       tm_hour += basestep;
1471
1472             break;
1473         case TMT_DAY:
1474             tm.       tm_mday += basestep;
1475
1476             break;
1477         case TMT_WEEK:
1478             tm.       tm_mday += 7 * basestep;
1479
1480             break;
1481         case TMT_MONTH:
1482             tm.       tm_mon += basestep;
1483
1484             break;
1485         case TMT_YEAR:
1486             tm.       tm_year += basestep;
1487         }
1488         madetime = mktime(&tm);
1489     } while (madetime == -1);   /* this is necessary to skip impssible times
1490                                    like the daylight saving time skips */
1491     return madetime;
1492
1493 }
1494
1495
1496 /* calculate values required for PRINT and GPRINT functions */
1497
1498 int print_calc(
1499     image_desc_t *im)
1500 {
1501     long      i, ii, validsteps;
1502     double    printval;
1503     struct tm tmvdef;
1504     int       graphelement = 0;
1505     long      vidx;
1506     int       max_ii;
1507     double    magfact = -1;
1508     char     *si_symb = "";
1509     char     *percent_s;
1510     int       prline_cnt = 0;
1511
1512     /* wow initializing tmvdef is quite a task :-) */
1513     time_t    now = time(NULL);
1514
1515     localtime_r(&now, &tmvdef);
1516     for (i = 0; i < im->gdes_c; i++) {
1517         vidx = im->gdes[i].vidx;
1518         switch (im->gdes[i].gf) {
1519         case GF_PRINT:
1520         case GF_GPRINT:
1521             /* PRINT and GPRINT can now print VDEF generated values.
1522              * There's no need to do any calculations on them as these
1523              * calculations were already made.
1524              */
1525             if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1526                 printval = im->gdes[vidx].vf.val;
1527                 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1528             } else {    /* need to calculate max,min,avg etcetera */
1529                 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1530                           / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1531                 printval = DNAN;
1532                 validsteps = 0;
1533                 for (ii = im->gdes[vidx].ds;
1534                      ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1535                     if (!finite(im->gdes[vidx].data[ii]))
1536                         continue;
1537                     if (isnan(printval)) {
1538                         printval = im->gdes[vidx].data[ii];
1539                         validsteps++;
1540                         continue;
1541                     }
1542
1543                     switch (im->gdes[i].cf) {
1544                     case CF_HWPREDICT:
1545                     case CF_MHWPREDICT:
1546                     case CF_DEVPREDICT:
1547                     case CF_DEVSEASONAL:
1548                     case CF_SEASONAL:
1549                     case CF_AVERAGE:
1550                         validsteps++;
1551                         printval += im->gdes[vidx].data[ii];
1552                         break;
1553                     case CF_MINIMUM:
1554                         printval = min(printval, im->gdes[vidx].data[ii]);
1555                         break;
1556                     case CF_FAILURES:
1557                     case CF_MAXIMUM:
1558                         printval = max(printval, im->gdes[vidx].data[ii]);
1559                         break;
1560                     case CF_LAST:
1561                         printval = im->gdes[vidx].data[ii];
1562                     }
1563                 }
1564                 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1565                     if (validsteps > 1) {
1566                         printval = (printval / validsteps);
1567                     }
1568                 }
1569             }           /* prepare printval */
1570
1571             if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1572                 /* Magfact is set to -1 upon entry to print_calc.  If it
1573                  * is still less than 0, then we need to run auto_scale.
1574                  * Otherwise, put the value into the correct units.  If
1575                  * the value is 0, then do not set the symbol or magnification
1576                  * so next the calculation will be performed again. */
1577                 if (magfact < 0.0) {
1578                     auto_scale(im, &printval, &si_symb, &magfact);
1579                     if (printval == 0.0)
1580                         magfact = -1.0;
1581                 } else {
1582                     printval /= magfact;
1583                 }
1584                 *(++percent_s) = 's';
1585             } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1586                 auto_scale(im, &printval, &si_symb, &magfact);
1587             }
1588
1589             if (im->gdes[i].gf == GF_PRINT) {
1590                 rrd_infoval_t prline;
1591
1592                 if (im->gdes[i].strftm) {
1593                     prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1594                     strftime(prline.u_str,
1595                              FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1596                 } else if (bad_format(im->gdes[i].format)) {
1597                     rrd_set_error
1598                         ("bad format for PRINT in '%s'", im->gdes[i].format);
1599                     return -1;
1600                 } else {
1601                     prline.u_str =
1602                         sprintf_alloc(im->gdes[i].format, printval, si_symb);
1603                 }
1604                 grinfo_push(im,
1605                             sprintf_alloc
1606                             ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1607                 free(prline.u_str);
1608             } else {
1609                 /* GF_GPRINT */
1610
1611                 if (im->gdes[i].strftm) {
1612                     strftime(im->gdes[i].legend,
1613                              FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1614                 } else {
1615                     if (bad_format(im->gdes[i].format)) {
1616                         rrd_set_error
1617                             ("bad format for GPRINT in '%s'",
1618                              im->gdes[i].format);
1619                         return -1;
1620                     }
1621 #ifdef HAVE_SNPRINTF
1622                     snprintf(im->gdes[i].legend,
1623                              FMT_LEG_LEN - 2,
1624                              im->gdes[i].format, printval, si_symb);
1625 #else
1626                     sprintf(im->gdes[i].legend,
1627                             im->gdes[i].format, printval, si_symb);
1628 #endif
1629                 }
1630                 graphelement = 1;
1631             }
1632             break;
1633         case GF_LINE:
1634         case GF_AREA:
1635         case GF_TICK:
1636             graphelement = 1;
1637             break;
1638         case GF_HRULE:
1639             if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1640                 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1641             };
1642             graphelement = 1;
1643             break;
1644         case GF_VRULE:
1645             if (im->gdes[i].xrule == 0) {   /* again ... the legend printer needs it */
1646                 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1647             };
1648             graphelement = 1;
1649             break;
1650         case GF_COMMENT:
1651         case GF_TEXTALIGN:
1652         case GF_DEF:
1653         case GF_CDEF:
1654         case GF_VDEF:
1655 #ifdef WITH_PIECHART
1656         case GF_PART:
1657 #endif
1658         case GF_SHIFT:
1659         case GF_XPORT:
1660             break;
1661         case GF_STACK:
1662             rrd_set_error
1663                 ("STACK should already be turned into LINE or AREA here");
1664             return -1;
1665             break;
1666         }
1667     }
1668     return graphelement;
1669 }
1670
1671
1672
1673 /* place legends with color spots */
1674 int leg_place(
1675     image_desc_t *im,
1676     int calc_width)
1677 {
1678     /* graph labels */
1679     int       interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1680     int       border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1681     int       fill = 0, fill_last;
1682     double    legendwidth; // = im->ximg - 2 * border;
1683     int       leg_c = 0;
1684     double    leg_x = border;
1685     int       leg_y = 0; //im->yimg;
1686     int       leg_y_prev = 0; // im->yimg;
1687     int       leg_cc;
1688     double    glue = 0;
1689     int       i, ii, mark = 0;
1690     char      default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1691     int      *legspace;
1692     char     *tab;
1693     char      saved_legend[FMT_LEG_LEN + 5];
1694
1695     if(calc_width){
1696         legendwidth = 0;
1697     }
1698     else{
1699         legendwidth = im->legendwidth - 2 * border;
1700     }
1701
1702
1703     if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1704         if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1705             rrd_set_error("malloc for legspace");
1706             return -1;
1707         }
1708
1709         for (i = 0; i < im->gdes_c; i++) {
1710             char      prt_fctn; /*special printfunctions */
1711             if(calc_width){
1712                 strcpy(saved_legend, im->gdes[i].legend);
1713             }
1714
1715             fill_last = fill;
1716             /* hide legends for rules which are not displayed */
1717             if (im->gdes[i].gf == GF_TEXTALIGN) {
1718                 default_txtalign = im->gdes[i].txtalign;
1719             }
1720
1721             if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1722                 if (im->gdes[i].gf == GF_HRULE
1723                     && (im->gdes[i].yrule <
1724                         im->minval || im->gdes[i].yrule > im->maxval))
1725                     im->gdes[i].legend[0] = '\0';
1726                 if (im->gdes[i].gf == GF_VRULE
1727                     && (im->gdes[i].xrule <
1728                         im->start || im->gdes[i].xrule > im->end))
1729                     im->gdes[i].legend[0] = '\0';
1730             }
1731
1732             /* turn \\t into tab */
1733             while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1734                 memmove(tab, tab + 1, strlen(tab));
1735                 tab[0] = (char) 9;
1736             }
1737
1738             leg_cc = strlen(im->gdes[i].legend);
1739             /* is there a controle code at the end of the legend string ? */
1740             if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1741                 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1742                 leg_cc -= 2;
1743                 im->gdes[i].legend[leg_cc] = '\0';
1744             } else {
1745                 prt_fctn = '\0';
1746             }
1747             /* only valid control codes */
1748             if (prt_fctn != 'l' && prt_fctn != 'n' &&   /* a synonym for l */
1749                 prt_fctn != 'r' &&
1750                 prt_fctn != 'j' &&
1751                 prt_fctn != 'c' &&
1752                 prt_fctn != 'u' &&
1753                 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1754                 free(legspace);
1755                 rrd_set_error
1756                     ("Unknown control code at the end of '%s\\%c'",
1757                      im->gdes[i].legend, prt_fctn);
1758                 return -1;
1759             }
1760             /* \n -> \l */
1761             if (prt_fctn == 'n') {
1762                 prt_fctn = 'l';
1763             }
1764
1765             /* remove exess space from the end of the legend for \g */
1766             while (prt_fctn == 'g' &&
1767                    leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1768                 leg_cc--;
1769                 im->gdes[i].legend[leg_cc] = '\0';
1770             }
1771
1772             if (leg_cc != 0) {
1773
1774                 /* no interleg space if string ends in \g */
1775                 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1776                 if (fill > 0) {
1777                     fill += legspace[i];
1778                 }
1779                 fill +=
1780                     gfx_get_text_width(im,
1781                                        fill + border,
1782                                        im->
1783                                        text_prop
1784                                        [TEXT_PROP_LEGEND].
1785                                        font_desc,
1786                                        im->tabwidth, im->gdes[i].legend);
1787                 leg_c++;
1788             } else {
1789                 legspace[i] = 0;
1790             }
1791             /* who said there was a special tag ... ? */
1792             if (prt_fctn == 'g') {
1793                 prt_fctn = '\0';
1794             }
1795
1796             if (prt_fctn == '\0') {
1797                 if(calc_width && (fill > legendwidth)){
1798                     legendwidth = fill;
1799                 }
1800                 if (i == im->gdes_c - 1 || fill > legendwidth) {
1801                     /* just one legend item is left right or center */
1802                     switch (default_txtalign) {
1803                     case TXA_RIGHT:
1804                         prt_fctn = 'r';
1805                         break;
1806                     case TXA_CENTER:
1807                         prt_fctn = 'c';
1808                         break;
1809                     case TXA_JUSTIFIED:
1810                         prt_fctn = 'j';
1811                         break;
1812                     default:
1813                         prt_fctn = 'l';
1814                         break;
1815                     }
1816                 }
1817                 /* is it time to place the legends ? */
1818                 if (fill > legendwidth) {
1819                     if (leg_c > 1) {
1820                         /* go back one */
1821                         i--;
1822                         fill = fill_last;
1823                         leg_c--;
1824                     }
1825                 }
1826                 if (leg_c == 1 && prt_fctn == 'j') {
1827                     prt_fctn = 'l';
1828                 }
1829             }
1830
1831             if (prt_fctn != '\0') {
1832                 leg_x = border;
1833                 if (leg_c >= 2 && prt_fctn == 'j') {
1834                     glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1835                 } else {
1836                     glue = 0;
1837                 }
1838                 if (prt_fctn == 'c')
1839                     leg_x = (double)(legendwidth - fill) / 2.0;
1840                 if (prt_fctn == 'r')
1841                     leg_x = legendwidth - fill + border;
1842                 for (ii = mark; ii <= i; ii++) {
1843                     if (im->gdes[ii].legend[0] == '\0')
1844                         continue;   /* skip empty legends */
1845                     im->gdes[ii].leg_x = leg_x;
1846                     im->gdes[ii].leg_y = leg_y + border;
1847                     leg_x +=
1848                         (double)gfx_get_text_width(im, leg_x,
1849                                            im->
1850                                            text_prop
1851                                            [TEXT_PROP_LEGEND].
1852                                            font_desc,
1853                                            im->tabwidth, im->gdes[ii].legend)
1854                         +(double)legspace[ii]
1855                         + glue;
1856                 }
1857                 leg_y_prev = leg_y;
1858                 if (leg_x > border || prt_fctn == 's')
1859                     leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1860                 if (prt_fctn == 's')
1861                     leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1862                 if (prt_fctn == 'u')
1863                     leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1864
1865                 if(calc_width && (fill > legendwidth)){
1866                     legendwidth = fill;
1867                 }
1868                 fill = 0;
1869                 leg_c = 0;
1870                 mark = ii;
1871             }
1872
1873             if(calc_width){
1874                 strcpy(im->gdes[i].legend, saved_legend);
1875             }
1876         }
1877
1878         if(calc_width){
1879             im->legendwidth = legendwidth + 2 * border;
1880         }
1881         else{
1882             im->legendheight = leg_y + border * 0.6;
1883         }
1884         free(legspace);
1885     }
1886     return 0;
1887 }
1888
1889 /* create a grid on the graph. it determines what to do
1890    from the values of xsize, start and end */
1891
1892 /* the xaxis labels are determined from the number of seconds per pixel
1893    in the requested graph */
1894
1895 int calc_horizontal_grid(
1896     image_desc_t
1897     *im)
1898 {
1899     double    range;
1900     double    scaledrange;
1901     int       pixel, i;
1902     int       gridind = 0;
1903     int       decimals, fractionals;
1904
1905     im->ygrid_scale.labfact = 2;
1906     range = im->maxval - im->minval;
1907     scaledrange = range / im->magfact;
1908     /* does the scale of this graph make it impossible to put lines
1909        on it? If so, give up. */
1910     if (isnan(scaledrange)) {
1911         return 0;
1912     }
1913
1914     /* find grid spaceing */
1915     pixel = 1;
1916     if (isnan(im->ygridstep)) {
1917         if (im->extra_flags & ALTYGRID) {
1918             /* find the value with max number of digits. Get number of digits */
1919             decimals =
1920                 ceil(log10
1921                      (max(fabs(im->maxval), fabs(im->minval)) *
1922                       im->viewfactor / im->magfact));
1923             if (decimals <= 0)  /* everything is small. make place for zero */
1924                 decimals = 1;
1925             im->ygrid_scale.gridstep =
1926                 pow((double) 10,
1927                     floor(log10(range * im->viewfactor / im->magfact))) /
1928                 im->viewfactor * im->magfact;
1929             if (im->ygrid_scale.gridstep == 0)  /* range is one -> 0.1 is reasonable scale */
1930                 im->ygrid_scale.gridstep = 0.1;
1931             /* should have at least 5 lines but no more then 15 */
1932             if (range / im->ygrid_scale.gridstep < 5
1933                 && im->ygrid_scale.gridstep >= 30)
1934                 im->ygrid_scale.gridstep /= 10;
1935             if (range / im->ygrid_scale.gridstep > 15)
1936                 im->ygrid_scale.gridstep *= 10;
1937             if (range / im->ygrid_scale.gridstep > 5) {
1938                 im->ygrid_scale.labfact = 1;
1939                 if (range / im->ygrid_scale.gridstep > 8
1940                     || im->ygrid_scale.gridstep <
1941                     1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1942                     im->ygrid_scale.labfact = 2;
1943             } else {
1944                 im->ygrid_scale.gridstep /= 5;
1945                 im->ygrid_scale.labfact = 5;
1946             }
1947             fractionals =
1948                 floor(log10
1949                       (im->ygrid_scale.gridstep *
1950                        (double) im->ygrid_scale.labfact * im->viewfactor /
1951                        im->magfact));
1952             if (fractionals < 0) {  /* small amplitude. */
1953                 int       len = decimals - fractionals + 1;
1954
1955                 if (im->unitslength < len + 2)
1956                     im->unitslength = len + 2;
1957                 sprintf(im->ygrid_scale.labfmt,
1958                         "%%%d.%df%s", len,
1959                         -fractionals, (im->symbol != ' ' ? " %c" : ""));
1960             } else {
1961                 int       len = decimals + 1;
1962
1963                 if (im->unitslength < len + 2)
1964                     im->unitslength = len + 2;
1965                 sprintf(im->ygrid_scale.labfmt,
1966                         "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1967             }
1968         } else {        /* classic rrd grid */
1969             for (i = 0; ylab[i].grid > 0; i++) {
1970                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1971                 gridind = i;
1972                 if (pixel >= 5)
1973                     break;
1974             }
1975
1976             for (i = 0; i < 4; i++) {
1977                 if (pixel * ylab[gridind].lfac[i] >=
1978                     1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1979                     im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1980                     break;
1981                 }
1982             }
1983
1984             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1985         }
1986     } else {
1987         im->ygrid_scale.gridstep = im->ygridstep;
1988         im->ygrid_scale.labfact = im->ylabfact;
1989     }
1990     return 1;
1991 }
1992
1993 int draw_horizontal_grid(
1994     image_desc_t
1995     *im)
1996 {
1997     int       i;
1998     double    scaledstep;
1999     char      graph_label[100];
2000     int       nlabels = 0;
2001     double    X0 = im->xorigin;
2002     double    X1 = im->xorigin + im->xsize;
2003     int       sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
2004     int       egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
2005     double    MaxY;
2006     double second_axis_magfact = 0;
2007     char *second_axis_symb = "";
2008
2009     scaledstep =
2010         im->ygrid_scale.gridstep /
2011         (double) im->magfact * (double) im->viewfactor;
2012     MaxY = scaledstep * (double) egrid;
2013     for (i = sgrid; i <= egrid; i++) {
2014         double    Y0 = ytr(im,
2015                            im->ygrid_scale.gridstep * i);
2016         double    YN = ytr(im,
2017                            im->ygrid_scale.gridstep * (i + 1));
2018
2019         if (floor(Y0 + 0.5) >=
2020             im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2021             /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2022                with the chosen settings. Add a label if required by settings, or if
2023                there is only one label so far and the next grid line is out of bounds. */
2024             if (i % im->ygrid_scale.labfact == 0
2025                 || (nlabels == 1
2026                     && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2027                 if (im->symbol == ' ') {
2028                     if (im->extra_flags & ALTYGRID) {
2029                         sprintf(graph_label,
2030                                 im->ygrid_scale.labfmt,
2031                                 scaledstep * (double) i);
2032                     } else {
2033                         if (MaxY < 10) {
2034                             sprintf(graph_label, "%4.1f",
2035                                     scaledstep * (double) i);
2036                         } else {
2037                             sprintf(graph_label, "%4.0f",
2038                                     scaledstep * (double) i);
2039                         }
2040                     }
2041                 } else {
2042                     char      sisym = (i == 0 ? ' ' : im->symbol);
2043
2044                     if (im->extra_flags & ALTYGRID) {
2045                         sprintf(graph_label,
2046                                 im->ygrid_scale.labfmt,
2047                                 scaledstep * (double) i, sisym);
2048                     } else {
2049                         if (MaxY < 10) {
2050                             sprintf(graph_label, "%4.1f %c",
2051                                     scaledstep * (double) i, sisym);
2052                         } else {
2053                             sprintf(graph_label, "%4.0f %c",
2054                                     scaledstep * (double) i, sisym);
2055                         }
2056                     }
2057                 }
2058                 nlabels++;
2059                 if (im->second_axis_scale != 0){
2060                         char graph_label_right[100];
2061                         double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2062                         if (im->second_axis_format[0] == '\0'){
2063                             if (!second_axis_magfact){
2064                                 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2065                                 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2066                             }
2067                             sval /= second_axis_magfact;
2068
2069                             if(MaxY < 10) {
2070                                 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2071                             } else {
2072                                 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2073                             }
2074                         }
2075                         else {
2076                            sprintf(graph_label_right,im->second_axis_format,sval);
2077                         }
2078                         gfx_text ( im,
2079                                X1+7, Y0,
2080                                im->graph_col[GRC_FONT],
2081                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2082                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2083                                graph_label_right );
2084                 }
2085
2086                 gfx_text(im,
2087                          X0 -
2088                          im->
2089                          text_prop[TEXT_PROP_AXIS].
2090                          size, Y0,
2091                          im->graph_col[GRC_FONT],
2092                          im->
2093                          text_prop[TEXT_PROP_AXIS].
2094                          font_desc,
2095                          im->tabwidth, 0.0,
2096                          GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2097                 gfx_line(im, X0 - 2, Y0, X0, Y0,
2098                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2099                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2100                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2101                 gfx_dashed_line(im, X0 - 2, Y0,
2102                                 X1 + 2, Y0,
2103                                 MGRIDWIDTH,
2104                                 im->
2105                                 graph_col
2106                                 [GRC_MGRID],
2107                                 im->grid_dash_on, im->grid_dash_off);
2108             } else if (!(im->extra_flags & NOMINOR)) {
2109                 gfx_line(im,
2110                          X0 - 2, Y0,
2111                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2112                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2113                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2114                 gfx_dashed_line(im, X0 - 1, Y0,
2115                                 X1 + 1, Y0,
2116                                 GRIDWIDTH,
2117                                 im->
2118                                 graph_col[GRC_GRID],
2119                                 im->grid_dash_on, im->grid_dash_off);
2120             }
2121         }
2122     }
2123     return 1;
2124 }
2125
2126 /* this is frexp for base 10 */
2127 double    frexp10(
2128     double,
2129     double *);
2130 double frexp10(
2131     double x,
2132     double *e)
2133 {
2134     double    mnt;
2135     int       iexp;
2136
2137     iexp = floor(log((double)fabs(x)) / log((double)10));
2138     mnt = x / pow(10.0, iexp);
2139     if (mnt >= 10.0) {
2140         iexp++;
2141         mnt = x / pow(10.0, iexp);
2142     }
2143     *e = iexp;
2144     return mnt;
2145 }
2146
2147
2148 /* logaritmic horizontal grid */
2149 int horizontal_log_grid(
2150     image_desc_t
2151     *im)
2152 {
2153     double    yloglab[][10] = {
2154         {
2155          1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2156          0.0, 0.0, 0.0}, {
2157                           1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2158                           0.0, 0.0, 0.0}, {
2159                                            1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2160                                            0.0, 0.0, 0.0}, {
2161                                                             1.0, 2.0, 4.0,
2162                                                             6.0, 8.0, 10.,
2163                                                             0.0,
2164                                                             0.0, 0.0, 0.0}, {
2165                                                                              1.0,
2166                                                                              2.0,
2167                                                                              3.0,
2168                                                                              4.0,
2169                                                                              5.0,
2170                                                                              6.0,
2171                                                                              7.0,
2172                                                                              8.0,
2173                                                                              9.0,
2174                                                                              10.},
2175         {
2176          0, 0, 0, 0, 0, 0, 0, 0, 0, 0}  /* last line */
2177     };
2178     int       i, j, val_exp, min_exp;
2179     double    nex;      /* number of decades in data */
2180     double    logscale; /* scale in logarithmic space */
2181     int       exfrac = 1;   /* decade spacing */
2182     int       mid = -1; /* row in yloglab for major grid */
2183     double    mspac;    /* smallest major grid spacing (pixels) */
2184     int       flab;     /* first value in yloglab to use */
2185     double    value, tmp, pre_value;
2186     double    X0, X1, Y0;
2187     char      graph_label[100];
2188
2189     nex = log10(im->maxval / im->minval);
2190     logscale = im->ysize / nex;
2191     /* major spacing for data with high dynamic range */
2192     while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2193         if (exfrac == 1)
2194             exfrac = 3;
2195         else
2196             exfrac += 3;
2197     }
2198
2199     /* major spacing for less dynamic data */
2200     do {
2201         /* search best row in yloglab */
2202         mid++;
2203         for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2204         mspac = logscale * log10(10.0 / yloglab[mid][i]);
2205     }
2206     while (mspac >
2207            2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2208     if (mid)
2209         mid--;
2210     /* find first value in yloglab */
2211     for (flab = 0;
2212          yloglab[mid][flab] < 10
2213          && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2214     if (yloglab[mid][flab] == 10.0) {
2215         tmp += 1.0;
2216         flab = 0;
2217     }
2218     val_exp = tmp;
2219     if (val_exp % exfrac)
2220         val_exp += abs(-val_exp % exfrac);
2221     X0 = im->xorigin;
2222     X1 = im->xorigin + im->xsize;
2223     /* draw grid */
2224     pre_value = DNAN;
2225     while (1) {
2226
2227         value = yloglab[mid][flab] * pow(10.0, val_exp);
2228         if (AlmostEqual2sComplement(value, pre_value, 4))
2229             break;      /* it seems we are not converging */
2230         pre_value = value;
2231         Y0 = ytr(im, value);
2232         if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2233             break;
2234         /* major grid line */
2235         gfx_line(im,
2236                  X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2237         gfx_line(im, X1, Y0, X1 + 2, Y0,
2238                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2239         gfx_dashed_line(im, X0 - 2, Y0,
2240                         X1 + 2, Y0,
2241                         MGRIDWIDTH,
2242                         im->
2243                         graph_col
2244                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2245         /* label */
2246         if (im->extra_flags & FORCE_UNITS_SI) {
2247             int       scale;
2248             double    pvalue;
2249             char      symbol;
2250
2251             scale = floor(val_exp / 3.0);
2252             if (value >= 1.0)
2253                 pvalue = pow(10.0, val_exp % 3);
2254             else
2255                 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2256             pvalue *= yloglab[mid][flab];
2257             if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2258                 && ((scale + si_symbcenter) >= 0))
2259                 symbol = si_symbol[scale + si_symbcenter];
2260             else
2261                 symbol = '?';
2262             sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2263         } else {
2264             sprintf(graph_label, "%3.0e", value);
2265         }
2266         if (im->second_axis_scale != 0){
2267                 char graph_label_right[100];
2268                 double sval = value*im->second_axis_scale+im->second_axis_shift;
2269                 if (im->second_axis_format[0] == '\0'){
2270                         if (im->extra_flags & FORCE_UNITS_SI) {
2271                                 double mfac = 1;
2272                                 char   *symb = "";
2273                                 auto_scale(im,&sval,&symb,&mfac);
2274                                 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2275                         }
2276                         else {
2277                                 sprintf(graph_label_right,"%3.0e", sval);
2278                         }
2279                 }
2280                 else {
2281                       sprintf(graph_label_right,im->second_axis_format,sval);
2282                 }
2283
2284                 gfx_text ( im,
2285                                X1+7, Y0,
2286                                im->graph_col[GRC_FONT],
2287                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2288                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2289                                graph_label_right );
2290         }
2291
2292         gfx_text(im,
2293                  X0 -
2294                  im->
2295                  text_prop[TEXT_PROP_AXIS].
2296                  size, Y0,
2297                  im->graph_col[GRC_FONT],
2298                  im->
2299                  text_prop[TEXT_PROP_AXIS].
2300                  font_desc,
2301                  im->tabwidth, 0.0,
2302                  GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2303         /* minor grid */
2304         if (mid < 4 && exfrac == 1) {
2305             /* find first and last minor line behind current major line
2306              * i is the first line and j tha last */
2307             if (flab == 0) {
2308                 min_exp = val_exp - 1;
2309                 for (i = 1; yloglab[mid][i] < 10.0; i++);
2310                 i = yloglab[mid][i - 1] + 1;
2311                 j = 10;
2312             } else {
2313                 min_exp = val_exp;
2314                 i = yloglab[mid][flab - 1] + 1;
2315                 j = yloglab[mid][flab];
2316             }
2317
2318             /* draw minor lines below current major line */
2319             for (; i < j; i++) {
2320
2321                 value = i * pow(10.0, min_exp);
2322                 if (value < im->minval)
2323                     continue;
2324                 Y0 = ytr(im, value);
2325                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2326                     break;
2327                 /* draw lines */
2328                 gfx_line(im,
2329                          X0 - 2, Y0,
2330                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2331                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2332                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2333                 gfx_dashed_line(im, X0 - 1, Y0,
2334                                 X1 + 1, Y0,
2335                                 GRIDWIDTH,
2336                                 im->
2337                                 graph_col[GRC_GRID],
2338                                 im->grid_dash_on, im->grid_dash_off);
2339             }
2340         } else if (exfrac > 1) {
2341             for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2342                 value = pow(10.0, i);
2343                 if (value < im->minval)
2344                     continue;
2345                 Y0 = ytr(im, value);
2346                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2347                     break;
2348                 /* draw lines */
2349                 gfx_line(im,
2350                          X0 - 2, Y0,
2351                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2352                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2353                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2354                 gfx_dashed_line(im, X0 - 1, Y0,
2355                                 X1 + 1, Y0,
2356                                 GRIDWIDTH,
2357                                 im->
2358                                 graph_col[GRC_GRID],
2359                                 im->grid_dash_on, im->grid_dash_off);
2360             }
2361         }
2362
2363         /* next decade */
2364         if (yloglab[mid][++flab] == 10.0) {
2365             flab = 0;
2366             val_exp += exfrac;
2367         }
2368     }
2369
2370     /* draw minor lines after highest major line */
2371     if (mid < 4 && exfrac == 1) {
2372         /* find first and last minor line below current major line
2373          * i is the first line and j tha last */
2374         if (flab == 0) {
2375             min_exp = val_exp - 1;
2376             for (i = 1; yloglab[mid][i] < 10.0; i++);
2377             i = yloglab[mid][i - 1] + 1;
2378             j = 10;
2379         } else {
2380             min_exp = val_exp;
2381             i = yloglab[mid][flab - 1] + 1;
2382             j = yloglab[mid][flab];
2383         }
2384
2385         /* draw minor lines below current major line */
2386         for (; i < j; i++) {
2387
2388             value = i * pow(10.0, min_exp);
2389             if (value < im->minval)
2390                 continue;
2391             Y0 = ytr(im, value);
2392             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2393                 break;
2394             /* draw lines */
2395             gfx_line(im,
2396                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2397             gfx_line(im, X1, Y0, X1 + 2, Y0,
2398                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2399             gfx_dashed_line(im, X0 - 1, Y0,
2400                             X1 + 1, Y0,
2401                             GRIDWIDTH,
2402                             im->
2403                             graph_col[GRC_GRID],
2404                             im->grid_dash_on, im->grid_dash_off);
2405         }
2406     }
2407     /* fancy minor gridlines */
2408     else if (exfrac > 1) {
2409         for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2410             value = pow(10.0, i);
2411             if (value < im->minval)
2412                 continue;
2413             Y0 = ytr(im, value);
2414             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2415                 break;
2416             /* draw lines */
2417             gfx_line(im,
2418                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2419             gfx_line(im, X1, Y0, X1 + 2, Y0,
2420                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2421             gfx_dashed_line(im, X0 - 1, Y0,
2422                             X1 + 1, Y0,
2423                             GRIDWIDTH,
2424                             im->
2425                             graph_col[GRC_GRID],
2426                             im->grid_dash_on, im->grid_dash_off);
2427         }
2428     }
2429
2430     return 1;
2431 }
2432
2433
2434 void vertical_grid(
2435     image_desc_t *im)
2436 {
2437     int       xlab_sel; /* which sort of label and grid ? */
2438     time_t    ti, tilab, timajor;
2439     long      factor;
2440     char      graph_label[100];
2441     double    X0, Y0, Y1;   /* points for filled graph and more */
2442     struct tm tm;
2443
2444     /* the type of time grid is determined by finding
2445        the number of seconds per pixel in the graph */
2446     if (im->xlab_user.minsec == -1) {
2447         factor = (im->end - im->start) / im->xsize;
2448         xlab_sel = 0;
2449         while (xlab[xlab_sel + 1].minsec !=
2450                -1 && xlab[xlab_sel + 1].minsec <= factor) {
2451             xlab_sel++;
2452         }               /* pick the last one */
2453         while (xlab[xlab_sel - 1].minsec ==
2454                xlab[xlab_sel].minsec
2455                && xlab[xlab_sel].length > (im->end - im->start)) {
2456             xlab_sel--;
2457         }               /* go back to the smallest size */
2458         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2459         im->xlab_user.gridst = xlab[xlab_sel].gridst;
2460         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2461         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2462         im->xlab_user.labtm = xlab[xlab_sel].labtm;
2463         im->xlab_user.labst = xlab[xlab_sel].labst;
2464         im->xlab_user.precis = xlab[xlab_sel].precis;
2465         im->xlab_user.stst = xlab[xlab_sel].stst;
2466     }
2467
2468     /* y coords are the same for every line ... */
2469     Y0 = im->yorigin;
2470     Y1 = im->yorigin - im->ysize;
2471     /* paint the minor grid */
2472     if (!(im->extra_flags & NOMINOR)) {
2473         for (ti = find_first_time(im->start,
2474                                   im->
2475                                   xlab_user.
2476                                   gridtm,
2477                                   im->
2478                                   xlab_user.
2479                                   gridst),
2480              timajor =
2481              find_first_time(im->start,
2482                              im->xlab_user.
2483                              mgridtm,
2484                              im->xlab_user.
2485                              mgridst);
2486              ti < im->end;
2487              ti =
2488              find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2489             ) {
2490             /* are we inside the graph ? */
2491             if (ti < im->start || ti > im->end)
2492                 continue;
2493             while (timajor < ti) {
2494                 timajor = find_next_time(timajor,
2495                                          im->
2496                                          xlab_user.
2497                                          mgridtm, im->xlab_user.mgridst);
2498             }
2499             if (ti == timajor)
2500                 continue;   /* skip as falls on major grid line */
2501             X0 = xtr(im, ti);
2502             gfx_line(im, X0, Y1 - 2, X0, Y1,
2503                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2504             gfx_line(im, X0, Y0, X0, Y0 + 2,
2505                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2506             gfx_dashed_line(im, X0, Y0 + 1, X0,
2507                             Y1 - 1, GRIDWIDTH,
2508                             im->
2509                             graph_col[GRC_GRID],
2510                             im->grid_dash_on, im->grid_dash_off);
2511         }
2512     }
2513
2514     /* paint the major grid */
2515     for (ti = find_first_time(im->start,
2516                               im->
2517                               xlab_user.
2518                               mgridtm,
2519                               im->
2520                               xlab_user.
2521                               mgridst);
2522          ti < im->end;
2523          ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2524         ) {
2525         /* are we inside the graph ? */
2526         if (ti < im->start || ti > im->end)
2527             continue;
2528         X0 = xtr(im, ti);
2529         gfx_line(im, X0, Y1 - 2, X0, Y1,
2530                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2531         gfx_line(im, X0, Y0, X0, Y0 + 3,
2532                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2533         gfx_dashed_line(im, X0, Y0 + 3, X0,
2534                         Y1 - 2, MGRIDWIDTH,
2535                         im->
2536                         graph_col
2537                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2538     }
2539     /* paint the labels below the graph */
2540     for (ti =
2541          find_first_time(im->start -
2542                          im->xlab_user.
2543                          precis / 2,
2544                          im->xlab_user.
2545                          labtm,
2546                          im->xlab_user.
2547                          labst);
2548          ti <=
2549          im->end -
2550          im->xlab_user.precis / 2;
2551          ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2552         ) {
2553         tilab = ti + im->xlab_user.precis / 2;  /* correct time for the label */
2554         /* are we inside the graph ? */
2555         if (tilab < im->start || tilab > im->end)
2556             continue;
2557 #if HAVE_STRFTIME
2558         localtime_r(&tilab, &tm);
2559         strftime(graph_label, 99, im->xlab_user.stst, &tm);
2560 #else
2561 # error "your libc has no strftime I guess we'll abort the exercise here."
2562 #endif
2563         gfx_text(im,
2564                  xtr(im, tilab),
2565                  Y0 + 3,
2566                  im->graph_col[GRC_FONT],
2567                  im->
2568                  text_prop[TEXT_PROP_AXIS].
2569                  font_desc,
2570                  im->tabwidth, 0.0,
2571                  GFX_H_CENTER, GFX_V_TOP, graph_label);
2572     }
2573
2574 }
2575
2576
2577 void axis_paint(
2578     image_desc_t *im)
2579 {
2580     /* draw x and y axis */
2581     /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2582        im->xorigin+im->xsize,im->yorigin-im->ysize,
2583        GRIDWIDTH, im->graph_col[GRC_AXIS]);
2584
2585        gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2586        im->xorigin+im->xsize,im->yorigin-im->ysize,
2587        GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2588
2589     gfx_line(im, im->xorigin - 4,
2590              im->yorigin,
2591              im->xorigin + im->xsize +
2592              4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2593     gfx_line(im, im->xorigin,
2594              im->yorigin + 4,
2595              im->xorigin,
2596              im->yorigin - im->ysize -
2597              4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2598     /* arrow for X and Y axis direction */
2599     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 */
2600                  im->graph_col[GRC_ARROW]);
2601     gfx_close_path(im);
2602     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 */
2603                  im->graph_col[GRC_ARROW]);
2604     gfx_close_path(im);
2605     if (im->second_axis_scale != 0){
2606        gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2607                          im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2608                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2609        gfx_new_area ( im,
2610                    im->xorigin+im->xsize-2,  im->yorigin-im->ysize-2,
2611                    im->xorigin+im->xsize+3,  im->yorigin-im->ysize-2,
2612                    im->xorigin+im->xsize,    im->yorigin-im->ysize-7, /* LINEOFFSET */
2613                    im->graph_col[GRC_ARROW]);
2614        gfx_close_path(im);
2615     }
2616
2617 }
2618
2619 void grid_paint(
2620     image_desc_t *im)
2621 {
2622     long      i;
2623     int       res = 0;
2624     double    X0, Y0;   /* points for filled graph and more */
2625     struct gfx_color_t water_color;
2626
2627     if (im->draw_3d_border > 0) {
2628             /* draw 3d border */
2629             i = im->draw_3d_border;
2630             gfx_new_area(im, 0, im->yimg,
2631                          i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2632             gfx_add_point(im, im->ximg - i, i);
2633             gfx_add_point(im, im->ximg, 0);
2634             gfx_add_point(im, 0, 0);
2635             gfx_close_path(im);
2636             gfx_new_area(im, i, im->yimg - i,
2637                          im->ximg - i,
2638                          im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2639             gfx_add_point(im, im->ximg, 0);
2640             gfx_add_point(im, im->ximg, im->yimg);
2641             gfx_add_point(im, 0, im->yimg);
2642             gfx_close_path(im);
2643     }
2644     if (im->draw_x_grid == 1)
2645         vertical_grid(im);
2646     if (im->draw_y_grid == 1) {
2647         if (im->logarithmic) {
2648             res = horizontal_log_grid(im);
2649         } else {
2650             res = draw_horizontal_grid(im);
2651         }
2652
2653         /* dont draw horizontal grid if there is no min and max val */
2654         if (!res) {
2655             char     *nodata = "No Data found";
2656
2657             gfx_text(im, im->ximg / 2,
2658                      (2 * im->yorigin -
2659                       im->ysize) / 2,
2660                      im->graph_col[GRC_FONT],
2661                      im->
2662                      text_prop[TEXT_PROP_AXIS].
2663                      font_desc,
2664                      im->tabwidth, 0.0,
2665                      GFX_H_CENTER, GFX_V_CENTER, nodata);
2666         }
2667     }
2668
2669     /* yaxis unit description */
2670     if (im->ylegend[0] != '\0'){
2671         gfx_text(im,
2672                  im->xOriginLegendY+10,
2673                  im->yOriginLegendY,
2674                  im->graph_col[GRC_FONT],
2675                  im->
2676                  text_prop[TEXT_PROP_UNIT].
2677                  font_desc,
2678                  im->tabwidth,
2679                  RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2680
2681     }
2682     if (im->second_axis_legend[0] != '\0'){
2683             gfx_text( im,
2684                   im->xOriginLegendY2+10,
2685                   im->yOriginLegendY2,
2686                   im->graph_col[GRC_FONT],
2687                   im->text_prop[TEXT_PROP_UNIT].font_desc,
2688                   im->tabwidth,
2689                   RRDGRAPH_YLEGEND_ANGLE,
2690                   GFX_H_CENTER, GFX_V_CENTER,
2691                   im->second_axis_legend);
2692     }
2693
2694     /* graph title */
2695     gfx_text(im,
2696              im->xOriginTitle, im->yOriginTitle+6,
2697              im->graph_col[GRC_FONT],
2698              im->
2699              text_prop[TEXT_PROP_TITLE].
2700              font_desc,
2701              im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2702     /* rrdtool 'logo' */
2703     if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2704         water_color = im->graph_col[GRC_FONT];
2705         water_color.alpha = 0.3;
2706         double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2707         gfx_text(im, xpos, 5,
2708                  water_color,
2709                  im->
2710                  text_prop[TEXT_PROP_WATERMARK].
2711                  font_desc, im->tabwidth,
2712                  -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2713     }
2714     /* graph watermark */
2715     if (im->watermark[0] != '\0') {
2716         water_color = im->graph_col[GRC_FONT];
2717         water_color.alpha = 0.3;
2718         gfx_text(im,
2719                  im->ximg / 2, im->yimg - 6,
2720                  water_color,
2721                  im->
2722                  text_prop[TEXT_PROP_WATERMARK].
2723                  font_desc, im->tabwidth, 0,
2724                  GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2725     }
2726
2727     /* graph labels */
2728     if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2729         for (i = 0; i < im->gdes_c; i++) {
2730             if (im->gdes[i].legend[0] == '\0')
2731                 continue;
2732             /* im->gdes[i].leg_y is the bottom of the legend */
2733             X0 = im->xOriginLegend + im->gdes[i].leg_x;
2734             Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2735             gfx_text(im, X0, Y0,
2736                      im->graph_col[GRC_FONT],
2737                      im->
2738                      text_prop
2739                      [TEXT_PROP_LEGEND].font_desc,
2740                      im->tabwidth, 0.0,
2741                      GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2742             /* The legend for GRAPH items starts with "M " to have
2743                enough space for the box */
2744             if (im->gdes[i].gf != GF_PRINT &&
2745                 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2746                 double    boxH, boxV;
2747                 double    X1, Y1;
2748
2749                 boxH = gfx_get_text_width(im, 0,
2750                                           im->
2751                                           text_prop
2752                                           [TEXT_PROP_LEGEND].
2753                                           font_desc,
2754                                           im->tabwidth, "o") * 1.2;
2755                 boxV = boxH;
2756                 /* shift the box up a bit */
2757                 Y0 -= boxV * 0.4;
2758
2759         if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */ 
2760                         cairo_save(im->cr);
2761                         cairo_new_path(im->cr);
2762                         cairo_set_line_width(im->cr, 1.0);
2763                         gfx_line(im,
2764                                 X0, Y0 - boxV / 2,
2765                                 X0 + boxH, Y0 - boxV / 2,
2766                                 1.0, im->gdes[i].col);
2767                         gfx_close_path(im);
2768                 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2769                         cairo_save(im->cr);
2770                         cairo_new_path(im->cr);
2771                         cairo_set_line_width(im->cr, 1.0);
2772                         gfx_line(im,
2773                                 X0 + boxH / 2, Y0,
2774                                 X0 + boxH / 2, Y0 - boxV,
2775                                 1.0, im->gdes[i].col);
2776                         gfx_close_path(im);
2777                 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2778                         cairo_save(im->cr);
2779                         cairo_new_path(im->cr);
2780                         cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2781                         gfx_line(im,
2782                                 X0, Y0,
2783                                 X0 + boxH, Y0 - boxV,
2784                                 im->gdes[i].linewidth, im->gdes[i].col);
2785                         gfx_close_path(im);
2786                 } else {
2787                 /* make sure transparent colors show up the same way as in the graph */
2788                         gfx_new_area(im,
2789                                      X0, Y0 - boxV,
2790                                      X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2791                         gfx_add_point(im, X0 + boxH, Y0 - boxV);
2792                         gfx_close_path(im);
2793                         gfx_new_area(im, X0, Y0 - boxV, X0,
2794                                      Y0, X0 + boxH, Y0, im->gdes[i].col);
2795                         gfx_add_point(im, X0 + boxH, Y0 - boxV);
2796                         gfx_close_path(im);
2797                         cairo_save(im->cr);
2798                         cairo_new_path(im->cr);
2799                         cairo_set_line_width(im->cr, 1.0);
2800                         X1 = X0 + boxH;
2801                         Y1 = Y0 - boxV;
2802                         gfx_line_fit(im, &X0, &Y0);
2803                         gfx_line_fit(im, &X1, &Y1);
2804                         cairo_move_to(im->cr, X0, Y0);
2805                         cairo_line_to(im->cr, X1, Y0);
2806                         cairo_line_to(im->cr, X1, Y1);
2807                         cairo_line_to(im->cr, X0, Y1);
2808                         cairo_close_path(im->cr);
2809                         cairo_set_source_rgba(im->cr,
2810                                               im->graph_col[GRC_FRAME].red,
2811                                               im->graph_col[GRC_FRAME].green,
2812                                               im->graph_col[GRC_FRAME].blue,
2813                                               im->graph_col[GRC_FRAME].alpha);
2814                 }
2815                 if (im->gdes[i].dash) {
2816                     /* make box borders in legend dashed if the graph is dashed */
2817                     double    dashes[] = {
2818                         3.0
2819                     };
2820                     cairo_set_dash(im->cr, dashes, 1, 0.0);
2821                 }
2822                 cairo_stroke(im->cr);
2823                 cairo_restore(im->cr);
2824             }
2825         }
2826     }
2827 }
2828
2829
2830 /*****************************************************
2831  * lazy check make sure we rely need to create this graph
2832  *****************************************************/
2833
2834 int lazy_check(
2835     image_desc_t *im)
2836 {
2837     FILE     *fd = NULL;
2838     int       size = 1;
2839     struct stat imgstat;
2840
2841     if (im->lazy == 0)
2842         return 0;       /* no lazy option */
2843     if (strlen(im->graphfile) == 0)
2844         return 0;       /* inmemory option */
2845     if (stat(im->graphfile, &imgstat) != 0)
2846         return 0;       /* can't stat */
2847     /* one pixel in the existing graph is more then what we would
2848        change here ... */
2849     if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2850         return 0;
2851     if ((fd = fopen(im->graphfile, "rb")) == NULL)
2852         return 0;       /* the file does not exist */
2853     switch (im->imgformat) {
2854     case IF_PNG:
2855         size = PngSize(fd, &(im->ximg), &(im->yimg));
2856         break;
2857     default:
2858         size = 1;
2859     }
2860     fclose(fd);
2861     return size;
2862 }
2863
2864
2865 int graph_size_location(
2866     image_desc_t
2867     *im,
2868     int elements)
2869 {
2870     /* The actual size of the image to draw is determined from
2871      ** several sources.  The size given on the command line is
2872      ** the graph area but we need more as we have to draw labels
2873      ** and other things outside the graph area. If the option
2874      ** --full-size-mode is selected the size defines the total
2875      ** image size and the size available for the graph is
2876      ** calculated.
2877      */
2878
2879     /** +---+-----------------------------------+
2880      ** | y |...............graph title.........|
2881      ** |   +---+-------------------------------+
2882      ** | a | y |                               |
2883      ** | x |   |                               |
2884      ** | i | a |                               |
2885      ** | s | x |       main graph area         |
2886      ** |   | i |                               |
2887      ** | t | s |                               |
2888      ** | i |   |                               |
2889      ** | t | l |                               |
2890      ** | l | b +-------------------------------+
2891      ** | e | l |       x axis labels           |
2892      ** +---+---+-------------------------------+
2893      ** |....................legends............|
2894      ** +---------------------------------------+
2895      ** |                   watermark           |
2896      ** +---------------------------------------+
2897      */
2898
2899     int       Xvertical = 0, Xvertical2 = 0, Ytitle =
2900         0, Xylabel = 0, Xmain = 0, Ymain =
2901         0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2902
2903     // no legends and no the shall be plotted it's easy
2904     if (im->extra_flags & ONLY_GRAPH) {
2905         im->xorigin = 0;
2906         im->ximg = im->xsize;
2907         im->yimg = im->ysize;
2908         im->yorigin = im->ysize;
2909         ytr(im, DNAN);
2910         return 0;
2911     }
2912
2913     if(im->watermark[0] != '\0') {
2914         Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2915     }
2916
2917     // calculate the width of the left vertical legend
2918     if (im->ylegend[0] != '\0') {
2919         Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2920     }
2921
2922     // calculate the width of the right vertical legend
2923     if (im->second_axis_legend[0] != '\0') {
2924         Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2925     }
2926     else{
2927         Xvertical2 = Xspacing;
2928     }
2929
2930     if (im->title[0] != '\0') {
2931         /* The title is placed "inbetween" two text lines so it
2932          ** automatically has some vertical spacing.  The horizontal
2933          ** spacing is added here, on each side.
2934          */
2935         /* if necessary, reduce the font size of the title until it fits the image width */
2936         Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2937     }
2938     else{
2939         // we have no title; get a little clearing from the top
2940         Ytitle = 1.5 * Yspacing;
2941     }
2942
2943     if (elements) {
2944         if (im->draw_x_grid) {
2945             // calculate the height of the horizontal labelling
2946             Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2947         }
2948         if (im->draw_y_grid || im->forceleftspace) {
2949             // calculate the width of the vertical labelling
2950             Xylabel =
2951                 gfx_get_text_width(im, 0,
2952                                    im->text_prop[TEXT_PROP_AXIS].font_desc,
2953                                    im->tabwidth, "0") * im->unitslength;
2954         }
2955     }
2956
2957     // add some space to the labelling
2958     Xylabel += Xspacing;
2959
2960     /* If the legend is printed besides the graph the width has to be
2961      ** calculated first. Placing the legend north or south of the
2962      ** graph requires the width calculation first, so the legend is
2963      ** skipped for the moment.
2964      */
2965     im->legendheight = 0;
2966     im->legendwidth = 0;
2967     if (!(im->extra_flags & NOLEGEND)) {
2968         if(im->legendposition == WEST || im->legendposition == EAST){
2969             if (leg_place(im, 1) == -1){
2970                 return -1;
2971             }
2972         }
2973     }
2974
2975     if (im->extra_flags & FULL_SIZE_MODE) {
2976
2977         /* The actual size of the image to draw has been determined by the user.
2978          ** The graph area is the space remaining after accounting for the legend,
2979          ** the watermark, the axis labels, and the title.
2980          */
2981         im->ximg = im->xsize;
2982         im->yimg = im->ysize;
2983         Xmain = im->ximg;
2984         Ymain = im->yimg;
2985
2986         /* Now calculate the total size.  Insert some spacing where
2987            desired.  im->xorigin and im->yorigin need to correspond
2988            with the lower left corner of the main graph area or, if
2989            this one is not set, the imaginary box surrounding the
2990            pie chart area. */
2991         /* Initial size calculation for the main graph area */
2992
2993         Xmain -= Xylabel;// + Xspacing;
2994         if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
2995             Xmain -= im->legendwidth;// + Xspacing;
2996         }
2997         if (im->second_axis_scale != 0){
2998             Xmain -= Xylabel;
2999         }
3000         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3001             Xmain -= Xspacing;
3002         }
3003
3004         Xmain -= Xvertical + Xvertical2;
3005
3006         /* limit the remaining space to 0 */
3007         if(Xmain < 1){
3008             Xmain = 1;
3009         }
3010         im->xsize = Xmain;
3011
3012         /* Putting the legend north or south, the height can now be calculated */
3013         if (!(im->extra_flags & NOLEGEND)) {
3014             if(im->legendposition == NORTH || im->legendposition == SOUTH){
3015                 im->legendwidth = im->ximg;
3016                 if (leg_place(im, 0) == -1){
3017                     return -1;
3018                 }
3019             }
3020         }
3021
3022         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3023             Ymain -=  Yxlabel + im->legendheight;
3024         }
3025         else{
3026             Ymain -= Yxlabel;
3027         }
3028
3029         /* reserve space for the title *or* some padding above the graph */
3030         Ymain -= Ytitle;
3031
3032             /* reserve space for padding below the graph */
3033         if (im->extra_flags & NOLEGEND) {
3034             Ymain -= Yspacing;
3035         }
3036
3037         if (im->watermark[0] != '\0') {
3038             Ymain -= Ywatermark;
3039         }
3040         /* limit the remaining height to 0 */
3041         if(Ymain < 1){
3042             Ymain = 1;
3043         }
3044         im->ysize = Ymain;
3045     } else {            /* dimension options -width and -height refer to the dimensions of the main graph area */
3046
3047         /* The actual size of the image to draw is determined from
3048          ** several sources.  The size given on the command line is
3049          ** the graph area but we need more as we have to draw labels
3050          ** and other things outside the graph area.
3051          */
3052
3053         if (elements) {
3054             Xmain = im->xsize; // + Xspacing;
3055             Ymain = im->ysize;
3056         }
3057
3058         im->ximg = Xmain + Xylabel;
3059         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3060             im->ximg += Xspacing;
3061         }
3062
3063         if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3064             im->ximg += im->legendwidth;// + Xspacing;
3065         }
3066         if (im->second_axis_scale != 0){
3067             im->ximg += Xylabel;
3068         }
3069
3070         im->ximg += Xvertical + Xvertical2;
3071
3072         if (!(im->extra_flags & NOLEGEND)) {
3073             if(im->legendposition == NORTH || im->legendposition == SOUTH){
3074                 im->legendwidth = im->ximg;
3075                 if (leg_place(im, 0) == -1){
3076                     return -1;
3077                 }
3078             }
3079         }
3080
3081         im->yimg = Ymain + Yxlabel;
3082         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3083              im->yimg += im->legendheight;
3084         }
3085
3086         /* reserve space for the title *or* some padding above the graph */
3087         if (Ytitle) {
3088             im->yimg += Ytitle;
3089         } else {
3090             im->yimg += 1.5 * Yspacing;
3091         }
3092         /* reserve space for padding below the graph */
3093         if (im->extra_flags & NOLEGEND) {
3094             im->yimg += Yspacing;
3095         }
3096
3097         if (im->watermark[0] != '\0') {
3098             im->yimg += Ywatermark;
3099         }
3100     }
3101
3102
3103     /* In case of putting the legend in west or east position the first
3104      ** legend calculation might lead to wrong positions if some items
3105      ** are not aligned on the left hand side (e.g. centered) as the
3106      ** legendwidth wight have been increased after the item was placed.
3107      ** In this case the positions have to be recalculated.
3108      */
3109     if (!(im->extra_flags & NOLEGEND)) {
3110         if(im->legendposition == WEST || im->legendposition == EAST){
3111             if (leg_place(im, 0) == -1){
3112                 return -1;
3113             }
3114         }
3115     }
3116
3117     /* After calculating all dimensions
3118      ** it is now possible to calculate
3119      ** all offsets.
3120      */
3121     switch(im->legendposition){
3122         case NORTH:
3123             im->xOriginTitle   = Xvertical + Xylabel + (im->xsize / 2);
3124             im->yOriginTitle   = 0;
3125
3126             im->xOriginLegend  = 0;
3127             im->yOriginLegend  = Ytitle;
3128
3129             im->xOriginLegendY = 0;
3130             im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3131
3132             im->xorigin        = Xvertical + Xylabel;
3133             im->yorigin        = Ytitle + im->legendheight + Ymain;
3134
3135             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3136             if (im->second_axis_scale != 0){
3137                 im->xOriginLegendY2 += Xylabel;
3138             }
3139             im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3140
3141             break;
3142
3143         case WEST:
3144             im->xOriginTitle   = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3145             im->yOriginTitle   = 0;
3146
3147             im->xOriginLegend  = 0;
3148             im->yOriginLegend  = Ytitle;
3149
3150             im->xOriginLegendY = im->legendwidth;
3151             im->yOriginLegendY = Ytitle + (Ymain / 2);
3152
3153             im->xorigin        = im->legendwidth + Xvertical + Xylabel;
3154             im->yorigin        = Ytitle + Ymain;
3155
3156             im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3157             if (im->second_axis_scale != 0){
3158                 im->xOriginLegendY2 += Xylabel;
3159             }
3160             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3161
3162             break;
3163
3164         case SOUTH:
3165             im->xOriginTitle   = Xvertical + Xylabel + im->xsize / 2;
3166             im->yOriginTitle   = 0;
3167
3168             im->xOriginLegend  = 0;
3169             im->yOriginLegend  = Ytitle + Ymain + Yxlabel;
3170
3171             im->xOriginLegendY = 0;
3172             im->yOriginLegendY = Ytitle + (Ymain / 2);
3173
3174             im->xorigin        = Xvertical + Xylabel;
3175             im->yorigin        = Ytitle + Ymain;
3176
3177             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3178             if (im->second_axis_scale != 0){
3179                 im->xOriginLegendY2 += Xylabel;
3180             }
3181             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3182
3183             break;
3184
3185         case EAST:
3186             im->xOriginTitle   = Xvertical + Xylabel + im->xsize / 2;
3187             im->yOriginTitle   = 0;
3188
3189             im->xOriginLegend  = Xvertical + Xylabel + Xmain + Xvertical2;
3190             if (im->second_axis_scale != 0){
3191                 im->xOriginLegend += Xylabel;
3192             }
3193             im->yOriginLegend  = Ytitle;
3194
3195             im->xOriginLegendY = 0;
3196             im->yOriginLegendY = Ytitle + (Ymain / 2);
3197
3198             im->xorigin        = Xvertical + Xylabel;
3199             im->yorigin        = Ytitle + Ymain;
3200
3201             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3202             if (im->second_axis_scale != 0){
3203                 im->xOriginLegendY2 += Xylabel;
3204             }
3205             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3206
3207             if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3208                 im->xOriginTitle    += Xspacing;
3209                 im->xOriginLegend   += Xspacing;
3210                 im->xOriginLegendY  += Xspacing;
3211                 im->xorigin         += Xspacing;
3212                 im->xOriginLegendY2 += Xspacing;
3213             }
3214             break;
3215     }
3216
3217     xtr(im, 0);
3218     ytr(im, DNAN);
3219     return 0;
3220 }
3221
3222 static cairo_status_t cairo_output(
3223     void *closure,
3224     const unsigned char
3225     *data,
3226     unsigned int length)
3227 {
3228     image_desc_t *im = (image_desc_t*)closure;
3229
3230     im->rendered_image =
3231         (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3232     if (im->rendered_image == NULL)
3233         return CAIRO_STATUS_WRITE_ERROR;
3234     memcpy(im->rendered_image + im->rendered_image_size, data, length);
3235     im->rendered_image_size += length;
3236     return CAIRO_STATUS_SUCCESS;
3237 }
3238
3239 /* draw that picture thing ... */
3240 int graph_paint(
3241     image_desc_t *im)
3242 {
3243     int       i, ii;
3244     int       lazy = lazy_check(im);
3245     double    areazero = 0.0;
3246     graph_desc_t *lastgdes = NULL;
3247     rrd_infoval_t info;
3248
3249 //    PangoFontMap *font_map = pango_cairo_font_map_get_default();
3250
3251     /* pull the data from the rrd files ... */
3252     if (data_fetch(im) == -1)
3253         return -1;
3254     /* evaluate VDEF and CDEF operations ... */
3255     if (data_calc(im) == -1)
3256         return -1;
3257     /* calculate and PRINT and GPRINT definitions. We have to do it at
3258      * this point because it will affect the length of the legends
3259      * if there are no graph elements (i==0) we stop here ...
3260      * if we are lazy, try to quit ...
3261      */
3262     i = print_calc(im);
3263     if (i < 0)
3264         return -1;
3265
3266     /* if we want and can be lazy ... quit now */
3267     if (i == 0)
3268         return 0;
3269
3270 /**************************************************************
3271  *** Calculating sizes and locations became a bit confusing ***
3272  *** so I moved this into a separate function.              ***
3273  **************************************************************/
3274     if (graph_size_location(im, i) == -1)
3275         return -1;
3276
3277     info.u_cnt = im->xorigin;
3278     grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3279     info.u_cnt = im->yorigin - im->ysize;
3280     grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3281     info.u_cnt = im->xsize;
3282     grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3283     info.u_cnt = im->ysize;
3284     grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3285     info.u_cnt = im->ximg;
3286     grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3287     info.u_cnt = im->yimg;
3288     grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3289     info.u_cnt = im->start;
3290     grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3291     info.u_cnt = im->end;
3292     grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3293
3294     /* if we want and can be lazy ... quit now */
3295     if (lazy)
3296         return 0;
3297
3298     /* get actual drawing data and find min and max values */
3299     if (data_proc(im) == -1)
3300         return -1;
3301     if (!im->logarithmic) {
3302         si_unit(im);
3303     }
3304
3305     /* identify si magnitude Kilo, Mega Giga ? */
3306     if (!im->rigid && !im->logarithmic)
3307         expand_range(im);   /* make sure the upper and lower limit are
3308                                sensible values */
3309
3310     info.u_val = im->minval;
3311     grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3312     info.u_val = im->maxval;
3313     grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3314
3315
3316     if (!calc_horizontal_grid(im))
3317         return -1;
3318     /* reset precalc */
3319     ytr(im, DNAN);
3320 /*   if (im->gridfit)
3321      apply_gridfit(im); */
3322     /* the actual graph is created by going through the individual
3323        graph elements and then drawing them */
3324     cairo_surface_destroy(im->surface);
3325     switch (im->imgformat) {
3326     case IF_PNG:
3327         im->surface =
3328             cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3329                                        im->ximg * im->zoom,
3330                                        im->yimg * im->zoom);
3331         break;
3332     case IF_PDF:
3333         im->gridfit = 0;
3334         im->surface = strlen(im->graphfile)
3335             ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3336                                        im->yimg * im->zoom)
3337             : cairo_pdf_surface_create_for_stream
3338             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3339         break;
3340     case IF_EPS:
3341         im->gridfit = 0;
3342         im->surface = strlen(im->graphfile)
3343             ?
3344             cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3345                                     im->yimg * im->zoom)
3346             : cairo_ps_surface_create_for_stream
3347             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3348         break;
3349     case IF_SVG:
3350         im->gridfit = 0;
3351         im->surface = strlen(im->graphfile)
3352             ?
3353             cairo_svg_surface_create(im->
3354                                      graphfile,
3355                                      im->ximg * im->zoom, im->yimg * im->zoom)
3356             : cairo_svg_surface_create_for_stream
3357             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3358         cairo_svg_surface_restrict_to_version
3359             (im->surface, CAIRO_SVG_VERSION_1_1);
3360         break;
3361     };
3362     cairo_destroy(im->cr);
3363     im->cr = cairo_create(im->surface);
3364     cairo_set_antialias(im->cr, im->graph_antialias);
3365     cairo_scale(im->cr, im->zoom, im->zoom);
3366 //    pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3367     gfx_new_area(im, 0, 0, 0, im->yimg,
3368                  im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3369     gfx_add_point(im, im->ximg, 0);
3370     gfx_close_path(im);
3371     gfx_new_area(im, im->xorigin,
3372                  im->yorigin,
3373                  im->xorigin +
3374                  im->xsize, im->yorigin,
3375                  im->xorigin +
3376                  im->xsize,
3377                  im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3378     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3379     gfx_close_path(im);
3380     cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3381                     im->xsize, im->ysize + 2.0);
3382     cairo_clip(im->cr);
3383     if (im->minval > 0.0)
3384         areazero = im->minval;
3385     if (im->maxval < 0.0)
3386         areazero = im->maxval;
3387     for (i = 0; i < im->gdes_c; i++) {
3388         switch (im->gdes[i].gf) {
3389         case GF_CDEF:
3390         case GF_VDEF:
3391         case GF_DEF:
3392         case GF_PRINT:
3393         case GF_GPRINT:
3394         case GF_COMMENT:
3395         case GF_TEXTALIGN:
3396         case GF_HRULE:
3397         case GF_VRULE:
3398         case GF_XPORT:
3399         case GF_SHIFT:
3400             break;
3401         case GF_TICK:
3402             for (ii = 0; ii < im->xsize; ii++) {
3403                 if (!isnan(im->gdes[i].p_data[ii])
3404                     && im->gdes[i].p_data[ii] != 0.0) {
3405                     if (im->gdes[i].yrule > 0) {
3406                         gfx_line(im,
3407                                  im->xorigin + ii,
3408                                  im->yorigin + 1.0,
3409                                  im->xorigin + ii,
3410                                  im->yorigin -
3411                                  im->gdes[i].yrule *
3412                                  im->ysize, 1.0, im->gdes[i].col);
3413                     } else if (im->gdes[i].yrule < 0) {
3414                         gfx_line(im,
3415                                  im->xorigin + ii,
3416                                  im->yorigin - im->ysize - 1.0,
3417                                  im->xorigin + ii,
3418                                  im->yorigin - im->ysize -
3419                                                 im->gdes[i].
3420                                                 yrule *
3421                                  im->ysize, 1.0, im->gdes[i].col);
3422                     }
3423                 }
3424             }
3425             break;
3426         case GF_LINE:
3427         case GF_AREA:
3428             /* fix data points at oo and -oo */
3429             for (ii = 0; ii < im->xsize; ii++) {
3430                 if (isinf(im->gdes[i].p_data[ii])) {
3431                     if (im->gdes[i].p_data[ii] > 0) {
3432                         im->gdes[i].p_data[ii] = im->maxval;
3433                     } else {
3434                         im->gdes[i].p_data[ii] = im->minval;
3435                     }
3436
3437                 }
3438             }           /* for */
3439
3440             /* *******************************************************
3441                a           ___. (a,t)
3442                |   |    ___
3443                ____|   |   |   |
3444                |       |___|
3445                -------|--t-1--t--------------------------------
3446
3447                if we know the value at time t was a then
3448                we draw a square from t-1 to t with the value a.
3449
3450                ********************************************************* */
3451             if (im->gdes[i].col.alpha != 0.0) {
3452                 /* GF_LINE and friend */
3453                 if (im->gdes[i].gf == GF_LINE) {
3454                     double    last_y = 0.0;
3455                     int       draw_on = 0;
3456
3457                     cairo_save(im->cr);
3458                     cairo_new_path(im->cr);
3459                     cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3460                     if (im->gdes[i].dash) {
3461                         cairo_set_dash(im->cr,
3462                                        im->gdes[i].p_dashes,
3463                                        im->gdes[i].ndash, im->gdes[i].offset);
3464                     }
3465
3466                     for (ii = 1; ii < im->xsize; ii++) {
3467                         if (isnan(im->gdes[i].p_data[ii])
3468                             || (im->slopemode == 1
3469                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3470                             draw_on = 0;
3471                             continue;
3472                         }
3473                         if (draw_on == 0) {
3474                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3475                             if (im->slopemode == 0) {
3476                                 double    x = ii - 1 + im->xorigin;
3477                                 double    y = last_y;
3478
3479                                 gfx_line_fit(im, &x, &y);
3480                                 cairo_move_to(im->cr, x, y);
3481                                 x = ii + im->xorigin;
3482                                 y = last_y;
3483                                 gfx_line_fit(im, &x, &y);
3484                                 cairo_line_to(im->cr, x, y);
3485                             } else {
3486                                 double    x = ii - 1 + im->xorigin;
3487                                 double    y =
3488                                     ytr(im, im->gdes[i].p_data[ii - 1]);
3489                                 gfx_line_fit(im, &x, &y);
3490                                 cairo_move_to(im->cr, x, y);
3491                                 x = ii + im->xorigin;
3492                                 y = last_y;
3493                                 gfx_line_fit(im, &x, &y);
3494                                 cairo_line_to(im->cr, x, y);
3495                             }
3496                             draw_on = 1;
3497                         } else {
3498                             double    x1 = ii + im->xorigin;
3499                             double    y1 = ytr(im, im->gdes[i].p_data[ii]);
3500
3501                             if (im->slopemode == 0
3502                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3503                                 double    x = ii - 1 + im->xorigin;
3504                                 double    y = y1;
3505
3506                                 gfx_line_fit(im, &x, &y);
3507                                 cairo_line_to(im->cr, x, y);
3508                             };
3509                             last_y = y1;
3510                             gfx_line_fit(im, &x1, &y1);
3511                             cairo_line_to(im->cr, x1, y1);
3512                         };
3513                     }
3514                     cairo_set_source_rgba(im->cr,
3515                                           im->gdes[i].
3516                                           col.red,
3517                                           im->gdes[i].
3518                                           col.green,
3519                                           im->gdes[i].
3520                                           col.blue, im->gdes[i].col.alpha);
3521                     cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3522                     cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3523                     cairo_stroke(im->cr);
3524                     cairo_restore(im->cr);
3525                 } else {
3526                     int       idxI = -1;
3527                     double   *foreY =
3528                         (double *) malloc(sizeof(double) * im->xsize * 2);
3529                     double   *foreX =
3530                         (double *) malloc(sizeof(double) * im->xsize * 2);
3531                     double   *backY =
3532                         (double *) malloc(sizeof(double) * im->xsize * 2);
3533                     double   *backX =
3534                         (double *) malloc(sizeof(double) * im->xsize * 2);
3535                     int       drawem = 0;
3536
3537                     for (ii = 0; ii <= im->xsize; ii++) {
3538                         double    ybase, ytop;
3539
3540                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3541                             int       cntI = 1;
3542                             int       lastI = 0;
3543
3544                             while (cntI < idxI
3545                                    &&
3546                                    AlmostEqual2sComplement(foreY
3547                                                            [lastI],
3548                                                            foreY[cntI], 4)
3549                                    &&
3550                                    AlmostEqual2sComplement(foreY
3551                                                            [lastI],
3552                                                            foreY
3553                                                            [cntI + 1], 4)) {
3554                                 cntI++;
3555                             }
3556                             gfx_new_area(im,
3557                                          backX[0], backY[0],
3558                                          foreX[0], foreY[0],
3559                                          foreX[cntI],
3560                                          foreY[cntI], im->gdes[i].col);
3561                             while (cntI < idxI) {
3562                                 lastI = cntI;
3563                                 cntI++;
3564                                 while (cntI < idxI
3565                                        &&
3566                                        AlmostEqual2sComplement(foreY
3567                                                                [lastI],
3568                                                                foreY[cntI], 4)
3569                                        &&
3570                                        AlmostEqual2sComplement(foreY
3571                                                                [lastI],
3572                                                                foreY
3573                                                                [cntI
3574                                                                 + 1], 4)) {
3575                                     cntI++;
3576                                 }
3577                                 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3578                             }
3579                             gfx_add_point(im, backX[idxI], backY[idxI]);
3580                             while (idxI > 1) {
3581                                 lastI = idxI;
3582                                 idxI--;
3583                                 while (idxI > 1
3584                                        &&
3585                                        AlmostEqual2sComplement(backY
3586                                                                [lastI],
3587                                                                backY[idxI], 4)
3588                                        &&
3589                                        AlmostEqual2sComplement(backY
3590                                                                [lastI],
3591                                                                backY
3592                                                                [idxI
3593                                                                 - 1], 4)) {
3594                                     idxI--;
3595                                 }
3596                                 gfx_add_point(im, backX[idxI], backY[idxI]);
3597                             }
3598                             idxI = -1;
3599                             drawem = 0;
3600                             gfx_close_path(im);
3601                         }
3602                         if (drawem != 0) {
3603                             drawem = 0;
3604                             idxI = -1;
3605                         }
3606                         if (ii == im->xsize)
3607                             break;
3608                         if (im->slopemode == 0 && ii == 0) {
3609                             continue;
3610                         }
3611                         if (isnan(im->gdes[i].p_data[ii])) {
3612                             drawem = 1;
3613                             continue;
3614                         }
3615                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3616                         if (lastgdes && im->gdes[i].stack) {
3617                             ybase = ytr(im, lastgdes->p_data[ii]);
3618                         } else {
3619                             ybase = ytr(im, areazero);
3620                         }
3621                         if (ybase == ytop) {
3622                             drawem = 1;
3623                             continue;
3624                         }
3625
3626                         if (ybase > ytop) {
3627                             double    extra = ytop;
3628
3629                             ytop = ybase;
3630                             ybase = extra;
3631                         }
3632                         if (im->slopemode == 0) {
3633                             backY[++idxI] = ybase - 0.2;
3634                             backX[idxI] = ii + im->xorigin - 1;
3635                             foreY[idxI] = ytop + 0.2;
3636                             foreX[idxI] = ii + im->xorigin - 1;
3637                         }
3638                         backY[++idxI] = ybase - 0.2;
3639                         backX[idxI] = ii + im->xorigin;
3640                         foreY[idxI] = ytop + 0.2;
3641                         foreX[idxI] = ii + im->xorigin;
3642                     }
3643                     /* close up any remaining area */
3644                     free(foreY);
3645                     free(foreX);
3646                     free(backY);
3647                     free(backX);
3648                 }       /* else GF_LINE */
3649             }
3650             /* if color != 0x0 */
3651             /* make sure we do not run into trouble when stacking on NaN */
3652             for (ii = 0; ii < im->xsize; ii++) {
3653                 if (isnan(im->gdes[i].p_data[ii])) {
3654                     if (lastgdes && (im->gdes[i].stack)) {
3655                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3656                     } else {
3657                         im->gdes[i].p_data[ii] = areazero;
3658                     }
3659                 }
3660             }
3661             lastgdes = &(im->gdes[i]);
3662             break;
3663         case GF_STACK:
3664             rrd_set_error
3665                 ("STACK should already be turned into LINE or AREA here");
3666             return -1;
3667             break;
3668         }               /* switch */
3669     }
3670     cairo_reset_clip(im->cr);
3671
3672     /* grid_paint also does the text */
3673     if (!(im->extra_flags & ONLY_GRAPH))
3674         grid_paint(im);
3675     if (!(im->extra_flags & ONLY_GRAPH))
3676         axis_paint(im);
3677     /* the RULES are the last thing to paint ... */
3678     for (i = 0; i < im->gdes_c; i++) {
3679
3680         switch (im->gdes[i].gf) {
3681         case GF_HRULE:
3682             if (im->gdes[i].yrule >= im->minval
3683                 && im->gdes[i].yrule <= im->maxval) {
3684                 cairo_save(im->cr);
3685                 if (im->gdes[i].dash) {
3686                     cairo_set_dash(im->cr,
3687                                    im->gdes[i].p_dashes,
3688                                    im->gdes[i].ndash, im->gdes[i].offset);
3689                 }
3690                 gfx_line(im, im->xorigin,
3691                          ytr(im, im->gdes[i].yrule),
3692                          im->xorigin + im->xsize,
3693                          ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3694                 cairo_stroke(im->cr);
3695                 cairo_restore(im->cr);
3696             }
3697             break;
3698         case GF_VRULE:
3699             if (im->gdes[i].xrule >= im->start
3700                 && im->gdes[i].xrule <= im->end) {
3701                 cairo_save(im->cr);
3702                 if (im->gdes[i].dash) {
3703                     cairo_set_dash(im->cr,
3704                                    im->gdes[i].p_dashes,
3705                                    im->gdes[i].ndash, im->gdes[i].offset);
3706                 }
3707                 gfx_line(im,
3708                          xtr(im, im->gdes[i].xrule),
3709                          im->yorigin, xtr(im,
3710                                           im->
3711                                           gdes[i].
3712                                           xrule),
3713                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3714                 cairo_stroke(im->cr);
3715                 cairo_restore(im->cr);
3716             }
3717             break;
3718         default:
3719             break;
3720         }
3721     }
3722
3723
3724     switch (im->imgformat) {
3725     case IF_PNG:
3726     {
3727         cairo_status_t status;
3728
3729         status = strlen(im->graphfile) ?
3730             cairo_surface_write_to_png(im->surface, im->graphfile)
3731             : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3732                                                 im);
3733
3734         if (status != CAIRO_STATUS_SUCCESS) {
3735             rrd_set_error("Could not save png to '%s'", im->graphfile);
3736             return 1;
3737         }
3738         break;
3739     }
3740     default:
3741         if (strlen(im->graphfile)) {
3742             cairo_show_page(im->cr);
3743         } else {
3744             cairo_surface_finish(im->surface);
3745         }
3746         break;
3747     }
3748
3749     return 0;
3750 }
3751
3752
3753 /*****************************************************
3754  * graph stuff
3755  *****************************************************/
3756
3757 int gdes_alloc(
3758     image_desc_t *im)
3759 {
3760
3761     im->gdes_c++;
3762     if ((im->gdes = (graph_desc_t *)
3763          rrd_realloc(im->gdes, (im->gdes_c)
3764                      * sizeof(graph_desc_t))) == NULL) {
3765         rrd_set_error("realloc graph_descs");
3766         return -1;
3767     }
3768
3769
3770     im->gdes[im->gdes_c - 1].step = im->step;
3771     im->gdes[im->gdes_c - 1].step_orig = im->step;
3772     im->gdes[im->gdes_c - 1].stack = 0;
3773     im->gdes[im->gdes_c - 1].linewidth = 0;
3774     im->gdes[im->gdes_c - 1].debug = 0;
3775     im->gdes[im->gdes_c - 1].start = im->start;
3776     im->gdes[im->gdes_c - 1].start_orig = im->start;
3777     im->gdes[im->gdes_c - 1].end = im->end;
3778     im->gdes[im->gdes_c - 1].end_orig = im->end;
3779     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3780     im->gdes[im->gdes_c - 1].data = NULL;
3781     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3782     im->gdes[im->gdes_c - 1].data_first = 0;
3783     im->gdes[im->gdes_c - 1].p_data = NULL;
3784     im->gdes[im->gdes_c - 1].rpnp = NULL;
3785     im->gdes[im->gdes_c - 1].p_dashes = NULL;
3786     im->gdes[im->gdes_c - 1].shift = 0.0;
3787     im->gdes[im->gdes_c - 1].dash = 0;
3788     im->gdes[im->gdes_c - 1].ndash = 0;
3789     im->gdes[im->gdes_c - 1].offset = 0;
3790     im->gdes[im->gdes_c - 1].col.red = 0.0;
3791     im->gdes[im->gdes_c - 1].col.green = 0.0;
3792     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3793     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3794     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3795     im->gdes[im->gdes_c - 1].format[0] = '\0';
3796     im->gdes[im->gdes_c - 1].strftm = 0;
3797     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3798     im->gdes[im->gdes_c - 1].ds = -1;
3799     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3800     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3801     im->gdes[im->gdes_c - 1].yrule = DNAN;
3802     im->gdes[im->gdes_c - 1].xrule = 0;
3803     return 0;
3804 }
3805
3806 /* copies input untill the first unescaped colon is found
3807    or until input ends. backslashes have to be escaped as well */
3808 int scan_for_col(
3809     const char *const input,
3810     int len,
3811     char *const output)
3812 {
3813     int       inp, outp = 0;
3814
3815     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3816         if (input[inp] == '\\'
3817             && input[inp + 1] != '\0'
3818             && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3819             output[outp++] = input[++inp];
3820         } else {
3821             output[outp++] = input[inp];
3822         }
3823     }
3824     output[outp] = '\0';
3825     return inp;
3826 }
3827
3828 /* Now just a wrapper around rrd_graph_v */
3829 int rrd_graph(
3830     int argc,
3831     char **argv,
3832     char ***prdata,
3833     int *xsize,
3834     int *ysize,
3835     FILE * stream,
3836     double *ymin,
3837     double *ymax)
3838 {
3839     int       prlines = 0;
3840     rrd_info_t *grinfo = NULL;
3841     rrd_info_t *walker;
3842
3843     grinfo = rrd_graph_v(argc, argv);
3844     if (grinfo == NULL)
3845         return -1;
3846     walker = grinfo;
3847     (*prdata) = NULL;
3848     while (walker) {
3849         if (strcmp(walker->key, "image_info") == 0) {
3850             prlines++;
3851             if (((*prdata) =
3852                  (char**)rrd_realloc((*prdata),
3853                              (prlines + 1) * sizeof(char *))) == NULL) {
3854                 rrd_set_error("realloc prdata");
3855                 return 0;
3856             }
3857             /* imginfo goes to position 0 in the prdata array */
3858             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3859                                              + 2) * sizeof(char));
3860             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3861             (*prdata)[prlines] = NULL;
3862         }
3863         /* skip anything else */
3864         walker = walker->next;
3865     }
3866     walker = grinfo;
3867     *xsize = 0;
3868     *ysize = 0;
3869     *ymin = 0;
3870     *ymax = 0;
3871     while (walker) {
3872         if (strcmp(walker->key, "image_width") == 0) {
3873             *xsize = walker->value.u_cnt;
3874         } else if (strcmp(walker->key, "image_height") == 0) {
3875             *ysize = walker->value.u_cnt;
3876         } else if (strcmp(walker->key, "value_min") == 0) {
3877             *ymin = walker->value.u_val;
3878         } else if (strcmp(walker->key, "value_max") == 0) {
3879             *ymax = walker->value.u_val;
3880         } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3881             prlines++;
3882             if (((*prdata) =
3883                  (char**)rrd_realloc((*prdata),
3884                              (prlines + 1) * sizeof(char *))) == NULL) {
3885                 rrd_set_error("realloc prdata");
3886                 return 0;
3887             }
3888             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3889                                              + 2) * sizeof(char));
3890             (*prdata)[prlines] = NULL;
3891             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3892         } else if (strcmp(walker->key, "image") == 0) {
3893             if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3894                    (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3895                 rrd_set_error("writing image");
3896                 return 0;
3897             }
3898         }
3899         /* skip anything else */
3900         walker = walker->next;
3901     }
3902     rrd_info_free(grinfo);
3903     return 0;
3904 }
3905
3906
3907 /* Some surgery done on this function, it became ridiculously big.
3908 ** Things moved:
3909 ** - initializing     now in rrd_graph_init()
3910 ** - options parsing  now in rrd_graph_options()
3911 ** - script parsing   now in rrd_graph_script()
3912 */
3913 rrd_info_t *rrd_graph_v(
3914     int argc,
3915     char **argv)
3916 {
3917     image_desc_t im;
3918     rrd_info_t *grinfo;
3919     char *old_locale;
3920     rrd_graph_init(&im);
3921     /* a dummy surface so that we can measure text sizes for placements */
3922     old_locale = setlocale(LC_NUMERIC, "C");
3923     rrd_graph_options(argc, argv, &im);
3924     if (rrd_test_error()) {
3925         rrd_info_free(im.grinfo);
3926         im_free(&im);
3927         return NULL;
3928     }
3929
3930     if (optind >= argc) {
3931         rrd_info_free(im.grinfo);
3932         im_free(&im);
3933         rrd_set_error("missing filename");
3934         return NULL;
3935     }
3936
3937     if (strlen(argv[optind]) >= MAXPATH) {
3938         rrd_set_error("filename (including path) too long");
3939         rrd_info_free(im.grinfo);
3940         im_free(&im);
3941         return NULL;
3942     }
3943
3944     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3945     im.graphfile[MAXPATH - 1] = '\0';
3946
3947     if (strcmp(im.graphfile, "-") == 0) {
3948         im.graphfile[0] = '\0';
3949     }
3950
3951     rrd_graph_script(argc, argv, &im, 1);
3952     setlocale(LC_NUMERIC, old_locale); /* reenable locale for rendering the graph */
3953
3954     if (rrd_test_error()) {
3955         rrd_info_free(im.grinfo);
3956         im_free(&im);
3957         return NULL;
3958     }
3959
3960     /* Everything is now read and the actual work can start */
3961
3962     if (graph_paint(&im) == -1) {
3963         rrd_info_free(im.grinfo);
3964         im_free(&im);
3965         return NULL;
3966     }
3967
3968
3969     /* The image is generated and needs to be output.
3970      ** Also, if needed, print a line with information about the image.
3971      */
3972
3973     if (im.imginfo) {
3974         rrd_infoval_t info;
3975         char     *path;
3976         char     *filename;
3977
3978         path = strdup(im.graphfile);
3979         filename = basename(path);
3980         info.u_str =
3981             sprintf_alloc(im.imginfo,
3982                           filename,
3983                           (long) (im.zoom *
3984                                   im.ximg), (long) (im.zoom * im.yimg));
3985         grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3986         free(info.u_str);
3987         free(path);
3988     }
3989     if (im.rendered_image) {
3990         rrd_infoval_t img;
3991
3992         img.u_blo.size = im.rendered_image_size;
3993         img.u_blo.ptr = im.rendered_image;
3994         grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3995     }
3996     grinfo = im.grinfo;
3997     im_free(&im);
3998     return grinfo;
3999 }
4000
4001 static void
4002 rrd_set_font_desc (
4003     image_desc_t *im,int prop,char *font, double size ){
4004     if (font){
4005         strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);
4006         im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';
4007         im->text_prop[prop].font_desc = pango_font_description_from_string( font );
4008     };
4009     if (size > 0){
4010         im->text_prop[prop].size = size;
4011     };
4012     if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
4013         pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
4014     };
4015 }
4016
4017 void rrd_graph_init(
4018     image_desc_t
4019     *im)
4020 {
4021     unsigned int i;
4022     char     *deffont = getenv("RRD_DEFAULT_FONT");
4023     static PangoFontMap *fontmap = NULL;
4024     PangoContext *context;
4025
4026 #ifdef HAVE_TZSET
4027     tzset();
4028 #endif
4029
4030     im->base = 1000;
4031     im->daemon_addr = NULL;
4032     im->draw_x_grid = 1;
4033     im->draw_y_grid = 1;
4034     im->draw_3d_border = 2;
4035     im->dynamic_labels = 0;
4036     im->extra_flags = 0;
4037     im->font_options = cairo_font_options_create();
4038     im->forceleftspace = 0;
4039     im->gdes_c = 0;
4040     im->gdes = NULL;
4041     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4042     im->grid_dash_off = 1;
4043     im->grid_dash_on = 1;
4044     im->gridfit = 1;
4045     im->grinfo = (rrd_info_t *) NULL;
4046     im->grinfo_current = (rrd_info_t *) NULL;
4047     im->imgformat = IF_PNG;
4048     im->imginfo = NULL;
4049     im->lazy = 0;
4050     im->legenddirection = TOP_DOWN;
4051     im->legendheight = 0;
4052     im->legendposition = SOUTH;
4053     im->legendwidth = 0;
4054     im->logarithmic = 0;
4055     im->maxval = DNAN;
4056     im->minval = 0;
4057     im->minval = DNAN;
4058     im->prt_c = 0;
4059     im->rigid = 0;
4060     im->rendered_image_size = 0;
4061     im->rendered_image = NULL;
4062     im->slopemode = 0;
4063     im->step = 0;
4064     im->symbol = ' ';
4065     im->tabwidth = 40.0;
4066     im->title[0] = '\0';
4067     im->unitsexponent = 9999;
4068     im->unitslength = 6;
4069     im->viewfactor = 1.0;
4070     im->watermark[0] = '\0';
4071     im->with_markup = 0;
4072     im->ximg = 0;
4073     im->xlab_user.minsec = -1;
4074     im->xorigin = 0;
4075     im->xOriginLegend = 0;
4076     im->xOriginLegendY = 0;
4077     im->xOriginLegendY2 = 0;
4078     im->xOriginTitle = 0;
4079     im->xsize = 400;
4080     im->ygridstep = DNAN;
4081     im->yimg = 0;
4082     im->ylegend[0] = '\0';
4083     im->second_axis_scale = 0; /* 0 disables it */
4084     im->second_axis_shift = 0; /* no shift by default */
4085     im->second_axis_legend[0] = '\0';
4086     im->second_axis_format[0] = '\0';
4087     im->yorigin = 0;
4088     im->yOriginLegend = 0;
4089     im->yOriginLegendY = 0;
4090     im->yOriginLegendY2 = 0;
4091     im->yOriginTitle = 0;
4092     im->ysize = 100;
4093     im->zoom = 1;
4094
4095     im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
4096     im->cr = cairo_create(im->surface);
4097
4098     for (i = 0; i < DIM(text_prop); i++) {
4099         im->text_prop[i].size = -1;
4100         rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4101     }
4102
4103     if (fontmap == NULL){
4104         fontmap = pango_cairo_font_map_get_default();
4105     }
4106
4107     context =  pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4108
4109     pango_cairo_context_set_resolution(context, 100);
4110
4111     pango_cairo_update_context(im->cr,context);
4112
4113     im->layout = pango_layout_new(context);
4114
4115 //  im->layout = pango_cairo_create_layout(im->cr);
4116
4117
4118     cairo_font_options_set_hint_style
4119         (im->font_options, CAIRO_HINT_STYLE_FULL);
4120     cairo_font_options_set_hint_metrics
4121         (im->font_options, CAIRO_HINT_METRICS_ON);
4122     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4123
4124
4125
4126     for (i = 0; i < DIM(graph_col); i++)
4127         im->graph_col[i] = graph_col[i];
4128
4129
4130 }
4131
4132
4133 void rrd_graph_options(
4134     int argc,
4135     char *argv[],
4136     image_desc_t
4137     *im)
4138 {
4139     int       stroff;
4140     char     *parsetime_error = NULL;
4141     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4142     time_t    start_tmp = 0, end_tmp = 0;
4143     long      long_tmp;
4144     rrd_time_value_t start_tv, end_tv;
4145     long unsigned int color;
4146
4147     /* defines for long options without a short equivalent. should be bytes,
4148        and may not collide with (the ASCII value of) short options */
4149 #define LONGOPT_UNITS_SI 255
4150
4151 /* *INDENT-OFF* */
4152     struct option long_options[] = {
4153         { "alt-autoscale",      no_argument,       0, 'A'},
4154         { "imgformat",          required_argument, 0, 'a'},
4155         { "font-smoothing-threshold", required_argument, 0, 'B'},
4156         { "base",               required_argument, 0, 'b'},
4157         { "color",              required_argument, 0, 'c'},
4158         { "full-size-mode",     no_argument,       0, 'D'},
4159         { "daemon",             required_argument, 0, 'd'},
4160         { "slope-mode",         no_argument,       0, 'E'},
4161         { "end",                required_argument, 0, 'e'},
4162         { "force-rules-legend", no_argument,       0, 'F'},
4163         { "imginfo",            required_argument, 0, 'f'},
4164         { "graph-render-mode",  required_argument, 0, 'G'},
4165         { "no-legend",          no_argument,       0, 'g'},
4166         { "height",             required_argument, 0, 'h'},
4167         { "no-minor",           no_argument,       0, 'I'},
4168         { "interlaced",         no_argument,       0, 'i'},
4169         { "alt-autoscale-min",  no_argument,       0, 'J'},
4170         { "only-graph",         no_argument,       0, 'j'},
4171         { "units-length",       required_argument, 0, 'L'},
4172         { "lower-limit",        required_argument, 0, 'l'},
4173         { "alt-autoscale-max",  no_argument,       0, 'M'},
4174         { "zoom",               required_argument, 0, 'm'},
4175         { "no-gridfit",         no_argument,       0, 'N'},
4176         { "font",               required_argument, 0, 'n'},
4177         { "logarithmic",        no_argument,       0, 'o'},
4178         { "pango-markup",       no_argument,       0, 'P'},
4179         { "font-render-mode",   required_argument, 0, 'R'},
4180         { "rigid",              no_argument,       0, 'r'},
4181         { "step",               required_argument, 0, 'S'},
4182         { "start",              required_argument, 0, 's'},
4183         { "tabwidth",           required_argument, 0, 'T'},
4184         { "title",              required_argument, 0, 't'},
4185         { "upper-limit",        required_argument, 0, 'u'},
4186         { "vertical-label",     required_argument, 0, 'v'},
4187         { "watermark",          required_argument, 0, 'W'},
4188         { "width",              required_argument, 0, 'w'},
4189         { "units-exponent",     required_argument, 0, 'X'},
4190         { "x-grid",             required_argument, 0, 'x'},
4191         { "alt-y-grid",         no_argument,       0, 'Y'},
4192         { "y-grid",             required_argument, 0, 'y'},
4193         { "lazy",               no_argument,       0, 'z'},
4194         { "units",              required_argument, 0, LONGOPT_UNITS_SI},
4195         { "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 */
4196         { "disable-rrdtool-tag",no_argument,       0, 1001},
4197         { "right-axis",         required_argument, 0, 1002},
4198         { "right-axis-label",   required_argument, 0, 1003},
4199         { "right-axis-format",  required_argument, 0, 1004},
4200         { "legend-position",    required_argument, 0, 1005},
4201         { "legend-direction",   required_argument, 0, 1006},
4202         { "border",             required_argument, 0, 1007},
4203         { "grid-dash",          required_argument, 0, 1008},
4204         { "dynamic-labels",     no_argument,       0, 1009},
4205         {  0, 0, 0, 0}
4206 };
4207 /* *INDENT-ON* */
4208
4209     optind = 0;
4210     opterr = 0;         /* initialize getopt */
4211     rrd_parsetime("end-24h", &start_tv);
4212     rrd_parsetime("now", &end_tv);
4213     while (1) {
4214         int       option_index = 0;
4215         int       opt;
4216         int       col_start, col_end;
4217
4218         opt = getopt_long(argc, argv,
4219                           "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",
4220                           long_options, &option_index);
4221         if (opt == EOF)
4222             break;
4223         switch (opt) {
4224         case 'I':
4225             im->extra_flags |= NOMINOR;
4226             break;
4227         case 'Y':
4228             im->extra_flags |= ALTYGRID;
4229             break;
4230         case 'A':
4231             im->extra_flags |= ALTAUTOSCALE;
4232             break;
4233         case 'J':
4234             im->extra_flags |= ALTAUTOSCALE_MIN;
4235             break;
4236         case 'M':
4237             im->extra_flags |= ALTAUTOSCALE_MAX;
4238             break;
4239         case 'j':
4240             im->extra_flags |= ONLY_GRAPH;
4241             break;
4242         case 'g':
4243             im->extra_flags |= NOLEGEND;
4244             break;
4245         case 1005:
4246             if (strcmp(optarg, "north") == 0) {
4247                 im->legendposition = NORTH;
4248             } else if (strcmp(optarg, "west") == 0) {
4249                 im->legendposition = WEST;
4250             } else if (strcmp(optarg, "south") == 0) {
4251                 im->legendposition = SOUTH;
4252             } else if (strcmp(optarg, "east") == 0) {
4253                 im->legendposition = EAST;
4254             } else {
4255                 rrd_set_error("unknown legend-position '%s'", optarg);
4256                 return;
4257             }
4258             break;
4259         case 1006:
4260             if (strcmp(optarg, "topdown") == 0) {
4261                 im->legenddirection = TOP_DOWN;
4262             } else if (strcmp(optarg, "bottomup") == 0) {
4263                 im->legenddirection = BOTTOM_UP;
4264             } else {
4265                 rrd_set_error("unknown legend-position '%s'", optarg);
4266                 return;
4267             }
4268             break;
4269         case 'F':
4270             im->extra_flags |= FORCE_RULES_LEGEND;
4271             break;
4272         case 1001:
4273             im->extra_flags |= NO_RRDTOOL_TAG;
4274             break;
4275         case LONGOPT_UNITS_SI:
4276             if (im->extra_flags & FORCE_UNITS) {
4277                 rrd_set_error("--units can only be used once!");
4278                 return;
4279             }
4280             if (strcmp(optarg, "si") == 0)
4281                 im->extra_flags |= FORCE_UNITS_SI;
4282             else {
4283                 rrd_set_error("invalid argument for --units: %s", optarg);
4284                 return;
4285             }
4286             break;
4287         case 'X':
4288             im->unitsexponent = atoi(optarg);
4289             break;
4290         case 'L':
4291             im->unitslength = atoi(optarg);
4292             im->forceleftspace = 1;
4293             break;
4294         case 'T':
4295             im->tabwidth = atof(optarg);
4296             break;
4297         case 'S':
4298             im->step = atoi(optarg);
4299             break;
4300         case 'N':
4301             im->gridfit = 0;
4302             break;
4303         case 'P':
4304             im->with_markup = 1;
4305             break;
4306         case 's':
4307             if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4308                 rrd_set_error("start time: %s", parsetime_error);
4309                 return;
4310             }
4311             break;
4312         case 'e':
4313             if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4314                 rrd_set_error("end time: %s", parsetime_error);
4315                 return;
4316             }
4317             break;
4318         case 'x':
4319             if (strcmp(optarg, "none") == 0) {
4320                 im->draw_x_grid = 0;
4321                 break;
4322             };
4323             if (sscanf(optarg,
4324                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4325                        scan_gtm,
4326                        &im->xlab_user.gridst,
4327                        scan_mtm,
4328                        &im->xlab_user.mgridst,
4329                        scan_ltm,
4330                        &im->xlab_user.labst,
4331                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4332                 strncpy(im->xlab_form, optarg + stroff,
4333                         sizeof(im->xlab_form) - 1);
4334                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4335                 if ((int)
4336                     (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4337                     rrd_set_error("unknown keyword %s", scan_gtm);
4338                     return;
4339                 } else if ((int)
4340                            (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4341                            == -1) {
4342                     rrd_set_error("unknown keyword %s", scan_mtm);
4343                     return;
4344                 } else if ((int)
4345                            (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4346                     rrd_set_error("unknown keyword %s", scan_ltm);
4347                     return;
4348                 }
4349                 im->xlab_user.minsec = 1;
4350                 im->xlab_user.stst = im->xlab_form;
4351             } else {
4352                 rrd_set_error("invalid x-grid format");
4353                 return;
4354             }
4355             break;
4356         case 'y':
4357
4358             if (strcmp(optarg, "none") == 0) {
4359                 im->draw_y_grid = 0;
4360                 break;
4361             };
4362             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4363                 if (im->ygridstep <= 0) {
4364                     rrd_set_error("grid step must be > 0");
4365                     return;
4366                 } else if (im->ylabfact < 1) {
4367                     rrd_set_error("label factor must be > 0");
4368                     return;
4369                 }
4370             } else {
4371                 rrd_set_error("invalid y-grid format");
4372                 return;
4373             }
4374             break;
4375         case 1007:
4376             im->draw_3d_border = atoi(optarg);
4377             break;
4378         case 1008: /* grid-dash */
4379             if(sscanf(optarg,
4380                       "%lf:%lf",
4381                       &im->grid_dash_on,
4382                       &im->grid_dash_off) != 2) {
4383                 rrd_set_error("expected grid-dash format float:float");
4384                 return;
4385             }
4386             break;   
4387         case 1009: /* enable dynamic labels */
4388             im->dynamic_labels = 1;
4389             break;         
4390         case 1002: /* right y axis */
4391
4392             if(sscanf(optarg,
4393                       "%lf:%lf",
4394                       &im->second_axis_scale,
4395                       &im->second_axis_shift) == 2) {
4396                 if(im->second_axis_scale==0){
4397                     rrd_set_error("the second_axis_scale  must not be 0");
4398                     return;
4399                 }
4400             } else {
4401                 rrd_set_error("invalid right-axis format expected scale:shift");
4402                 return;
4403             }
4404             break;
4405         case 1003:
4406             strncpy(im->second_axis_legend,optarg,150);
4407             im->second_axis_legend[150]='\0';
4408             break;
4409         case 1004:
4410             if (bad_format(optarg)){
4411                 rrd_set_error("use either %le or %lf formats");
4412                 return;
4413             }
4414             strncpy(im->second_axis_format,optarg,150);
4415             im->second_axis_format[150]='\0';
4416             break;
4417         case 'v':
4418             strncpy(im->ylegend, optarg, 150);
4419             im->ylegend[150] = '\0';
4420             break;
4421         case 'u':
4422             im->maxval = atof(optarg);
4423             break;
4424         case 'l':
4425             im->minval = atof(optarg);
4426             break;
4427         case 'b':
4428             im->base = atol(optarg);
4429             if (im->base != 1024 && im->base != 1000) {
4430                 rrd_set_error
4431                     ("the only sensible value for base apart from 1000 is 1024");
4432                 return;
4433             }
4434             break;
4435         case 'w':
4436             long_tmp = atol(optarg);
4437             if (long_tmp < 10) {
4438                 rrd_set_error("width below 10 pixels");
4439                 return;
4440             }
4441             im->xsize = long_tmp;
4442             break;
4443         case 'h':
4444             long_tmp = atol(optarg);
4445             if (long_tmp < 10) {
4446                 rrd_set_error("height below 10 pixels");
4447                 return;
4448             }
4449             im->ysize = long_tmp;
4450             break;
4451         case 'D':
4452             im->extra_flags |= FULL_SIZE_MODE;
4453             break;
4454         case 'i':
4455             /* interlaced png not supported at the moment */
4456             break;
4457         case 'r':
4458             im->rigid = 1;
4459             break;
4460         case 'f':
4461             im->imginfo = optarg;
4462             break;
4463         case 'a':
4464             if ((int)
4465                 (im->imgformat = if_conv(optarg)) == -1) {
4466                 rrd_set_error("unsupported graphics format '%s'", optarg);
4467                 return;
4468             }
4469             break;
4470         case 'z':
4471             im->lazy = 1;
4472             break;
4473         case 'E':
4474             im->slopemode = 1;
4475             break;
4476         case 'o':
4477             im->logarithmic = 1;
4478             break;
4479         case 'c':
4480             if (sscanf(optarg,
4481                        "%10[A-Z]#%n%8lx%n",
4482                        col_nam, &col_start, &color, &col_end) == 2) {
4483                 int       ci;
4484                 int       col_len = col_end - col_start;
4485
4486                 switch (col_len) {
4487                 case 3:
4488                     color =
4489                         (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4490                                                          0x011000) |
4491                          ((color & 0x00F)
4492                           * 0x001100)
4493                          | 0x000000FF);
4494                     break;
4495                 case 4:
4496                     color =
4497                         (((color & 0xF000) *
4498                           0x11000) | ((color & 0x0F00) *
4499                                       0x01100) | ((color &
4500                                                    0x00F0) *
4501                                                   0x00110) |
4502                          ((color & 0x000F) * 0x00011)
4503                         );
4504                     break;
4505                 case 6:
4506                     color = (color << 8) + 0xff /* shift left by 8 */ ;
4507                     break;
4508                 case 8:
4509                     break;
4510                 default:
4511                     rrd_set_error("the color format is #RRGGBB[AA]");
4512                     return;
4513                 }
4514                 if ((ci = grc_conv(col_nam)) != -1) {
4515                     im->graph_col[ci] = gfx_hex_to_col(color);
4516                 } else {
4517                     rrd_set_error("invalid color name '%s'", col_nam);
4518                     return;
4519                 }
4520             } else {
4521                 rrd_set_error("invalid color def format");
4522                 return;
4523             }
4524             break;
4525         case 'n':{
4526             char      prop[15];
4527             double    size = 1;
4528             int       end;
4529
4530             if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4531                 int       sindex, propidx;
4532
4533                 if ((sindex = text_prop_conv(prop)) != -1) {
4534                     for (propidx = sindex;
4535                          propidx < TEXT_PROP_LAST; propidx++) {
4536                         if (size > 0) {
4537                             rrd_set_font_desc(im,propidx,NULL,size);
4538                         }
4539                         if ((int) strlen(optarg) > end+2) {
4540                             if (optarg[end] == ':') {
4541                                 rrd_set_font_desc(im,propidx,optarg + end + 1,0);
4542                             } else {
4543                                 rrd_set_error
4544                                     ("expected : after font size in '%s'",
4545                                      optarg);
4546                                 return;
4547                             }
4548                         }
4549                         /* only run the for loop for DEFAULT (0) for
4550                            all others, we break here. woodo programming */
4551                         if (propidx == sindex && sindex != 0)
4552                             break;
4553                     }
4554                 } else {
4555                     rrd_set_error("invalid fonttag '%s'", prop);
4556                     return;
4557                 }
4558             } else {
4559                 rrd_set_error("invalid text property format");
4560                 return;
4561             }
4562             break;
4563         }
4564         case 'm':
4565             im->zoom = atof(optarg);
4566             if (im->zoom <= 0.0) {
4567                 rrd_set_error("zoom factor must be > 0");
4568                 return;
4569             }
4570             break;
4571         case 't':
4572             strncpy(im->title, optarg, 150);
4573             im->title[150] = '\0';
4574             break;
4575         case 'R':
4576             if (strcmp(optarg, "normal") == 0) {
4577                 cairo_font_options_set_antialias
4578                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4579                 cairo_font_options_set_hint_style
4580                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4581             } else if (strcmp(optarg, "light") == 0) {
4582                 cairo_font_options_set_antialias
4583                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4584                 cairo_font_options_set_hint_style
4585                     (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4586             } else if (strcmp(optarg, "mono") == 0) {
4587                 cairo_font_options_set_antialias
4588                     (im->font_options, CAIRO_ANTIALIAS_NONE);
4589                 cairo_font_options_set_hint_style
4590                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4591             } else {
4592                 rrd_set_error("unknown font-render-mode '%s'", optarg);
4593                 return;
4594             }
4595             break;
4596         case 'G':
4597             if (strcmp(optarg, "normal") == 0)
4598                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4599             else if (strcmp(optarg, "mono") == 0)
4600                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4601             else {
4602                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4603                 return;
4604             }
4605             break;
4606         case 'B':
4607             /* not supported curently */
4608             break;
4609         case 'W':
4610             strncpy(im->watermark, optarg, 100);
4611             im->watermark[99] = '\0';
4612             break;
4613         case 'd':
4614         {
4615             if (im->daemon_addr != NULL)
4616             {
4617                 rrd_set_error ("You cannot specify --daemon "
4618                         "more than once.");
4619                 return;
4620             }
4621
4622             im->daemon_addr = strdup(optarg);
4623             if (im->daemon_addr == NULL)
4624             {
4625               rrd_set_error("strdup failed");
4626               return;
4627             }
4628
4629             break;
4630         }
4631         case '?':
4632             if (optopt != 0)
4633                 rrd_set_error("unknown option '%c'", optopt);
4634             else
4635                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4636             return;
4637         }
4638     } /* while (1) */
4639
4640     {   /* try to connect to rrdcached */
4641         int status = rrdc_connect(im->daemon_addr);
4642         if (status != 0) return;
4643     }
4644
4645     pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4646     pango_layout_context_changed(im->layout);
4647
4648
4649
4650     if (im->logarithmic && im->minval <= 0) {
4651         rrd_set_error
4652             ("for a logarithmic yaxis you must specify a lower-limit > 0");
4653         return;
4654     }
4655
4656     if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4657         /* error string is set in rrd_parsetime.c */
4658         return;
4659     }
4660
4661     if (start_tmp < 3600 * 24 * 365 * 10) {
4662         rrd_set_error
4663             ("the first entry to fetch should be after 1980 (%ld)",
4664              start_tmp);
4665         return;
4666     }
4667
4668     if (end_tmp < start_tmp) {
4669         rrd_set_error
4670             ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4671         return;
4672     }
4673
4674     im->start = start_tmp;
4675     im->end = end_tmp;
4676     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4677 }
4678
4679 int rrd_graph_color(
4680     image_desc_t
4681     *im,
4682     char *var,
4683     char *err,
4684     int optional)
4685 {
4686     char     *color;
4687     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4688
4689     color = strstr(var, "#");
4690     if (color == NULL) {
4691         if (optional == 0) {
4692             rrd_set_error("Found no color in %s", err);
4693             return 0;
4694         }
4695         return 0;
4696     } else {
4697         int       n = 0;
4698         char     *rest;
4699         long unsigned int col;
4700
4701         rest = strstr(color, ":");
4702         if (rest != NULL)
4703             n = rest - color;
4704         else
4705             n = strlen(color);
4706         switch (n) {
4707         case 7:
4708             sscanf(color, "#%6lx%n", &col, &n);
4709             col = (col << 8) + 0xff /* shift left by 8 */ ;
4710             if (n != 7)
4711                 rrd_set_error("Color problem in %s", err);
4712             break;
4713         case 9:
4714             sscanf(color, "#%8lx%n", &col, &n);
4715             if (n == 9)
4716                 break;
4717         default:
4718             rrd_set_error("Color problem in %s", err);
4719         }
4720         if (rrd_test_error())
4721             return 0;
4722         gdp->col = gfx_hex_to_col(col);
4723         return n;
4724     }
4725 }
4726
4727
4728 int bad_format(
4729     char *fmt)
4730 {
4731     char     *ptr;
4732     int       n = 0;
4733
4734     ptr = fmt;
4735     while (*ptr != '\0')
4736         if (*ptr++ == '%') {
4737
4738             /* line cannot end with percent char */
4739             if (*ptr == '\0')
4740                 return 1;
4741             /* '%s', '%S' and '%%' are allowed */
4742             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4743                 ptr++;
4744             /* %c is allowed (but use only with vdef!) */
4745             else if (*ptr == 'c') {
4746                 ptr++;
4747                 n = 1;
4748             }
4749
4750             /* or else '% 6.2lf' and such are allowed */
4751             else {
4752                 /* optional padding character */
4753                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4754                     ptr++;
4755                 /* This should take care of 'm.n' with all three optional */
4756                 while (*ptr >= '0' && *ptr <= '9')
4757                     ptr++;
4758                 if (*ptr == '.')
4759                     ptr++;
4760                 while (*ptr >= '0' && *ptr <= '9')
4761                     ptr++;
4762                 /* Either 'le', 'lf' or 'lg' must follow here */
4763                 if (*ptr++ != 'l')
4764                     return 1;
4765                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4766                     ptr++;
4767                 else
4768                     return 1;
4769                 n++;
4770             }
4771         }
4772
4773     return (n != 1);
4774 }
4775
4776
4777 int vdef_parse(
4778     struct graph_desc_t
4779     *gdes,
4780     const char *const str)
4781 {
4782     /* A VDEF currently is either "func" or "param,func"
4783      * so the parsing is rather simple.  Change if needed.
4784      */
4785     double    param;
4786     char      func[30];
4787     int       n;
4788
4789     n = 0;
4790     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4791     if (n == (int) strlen(str)) {   /* matched */
4792         ;
4793     } else {
4794         n = 0;
4795         sscanf(str, "%29[A-Z]%n", func, &n);
4796         if (n == (int) strlen(str)) {   /* matched */
4797             param = DNAN;
4798         } else {
4799             rrd_set_error
4800                 ("Unknown function string '%s' in VDEF '%s'",
4801                  str, gdes->vname);
4802             return -1;
4803         }
4804     }
4805     if (!strcmp("PERCENT", func))
4806         gdes->vf.op = VDEF_PERCENT;
4807     else if (!strcmp("PERCENTNAN", func))
4808         gdes->vf.op = VDEF_PERCENTNAN;
4809     else if (!strcmp("MAXIMUM", func))
4810         gdes->vf.op = VDEF_MAXIMUM;
4811     else if (!strcmp("AVERAGE", func))
4812         gdes->vf.op = VDEF_AVERAGE;
4813     else if (!strcmp("STDEV", func))
4814         gdes->vf.op = VDEF_STDEV;
4815     else if (!strcmp("MINIMUM", func))
4816         gdes->vf.op = VDEF_MINIMUM;
4817     else if (!strcmp("TOTAL", func))
4818         gdes->vf.op = VDEF_TOTAL;
4819     else if (!strcmp("FIRST", func))
4820         gdes->vf.op = VDEF_FIRST;
4821     else if (!strcmp("LAST", func))
4822         gdes->vf.op = VDEF_LAST;
4823     else if (!strcmp("LSLSLOPE", func))
4824         gdes->vf.op = VDEF_LSLSLOPE;
4825     else if (!strcmp("LSLINT", func))
4826         gdes->vf.op = VDEF_LSLINT;
4827     else if (!strcmp("LSLCORREL", func))
4828         gdes->vf.op = VDEF_LSLCORREL;
4829     else {
4830         rrd_set_error
4831             ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4832         return -1;
4833     };
4834     switch (gdes->vf.op) {
4835     case VDEF_PERCENT:
4836     case VDEF_PERCENTNAN:
4837         if (isnan(param)) { /* no parameter given */
4838             rrd_set_error
4839                 ("Function '%s' needs parameter in VDEF '%s'\n",
4840                  func, gdes->vname);
4841             return -1;
4842         };
4843         if (param >= 0.0 && param <= 100.0) {
4844             gdes->vf.param = param;
4845             gdes->vf.val = DNAN;    /* undefined */
4846             gdes->vf.when = 0;  /* undefined */
4847         } else {
4848             rrd_set_error
4849                 ("Parameter '%f' out of range in VDEF '%s'\n",
4850                  param, gdes->vname);
4851             return -1;
4852         };
4853         break;
4854     case VDEF_MAXIMUM:
4855     case VDEF_AVERAGE:
4856     case VDEF_STDEV:
4857     case VDEF_MINIMUM:
4858     case VDEF_TOTAL:
4859     case VDEF_FIRST:
4860     case VDEF_LAST:
4861     case VDEF_LSLSLOPE:
4862     case VDEF_LSLINT:
4863     case VDEF_LSLCORREL:
4864         if (isnan(param)) {
4865             gdes->vf.param = DNAN;
4866             gdes->vf.val = DNAN;
4867             gdes->vf.when = 0;
4868         } else {
4869             rrd_set_error
4870                 ("Function '%s' needs no parameter in VDEF '%s'\n",
4871                  func, gdes->vname);
4872             return -1;
4873         };
4874         break;
4875     };
4876     return 0;
4877 }
4878
4879
4880 int vdef_calc(
4881     image_desc_t *im,
4882     int gdi)
4883 {
4884     graph_desc_t *src, *dst;
4885     rrd_value_t *data;
4886     long      step, steps;
4887
4888     dst = &im->gdes[gdi];
4889     src = &im->gdes[dst->vidx];
4890     data = src->data + src->ds;
4891
4892     steps = (src->end - src->start) / src->step;
4893 #if 0
4894     printf
4895         ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4896          src->start, src->end, steps);
4897 #endif
4898     switch (dst->vf.op) {
4899     case VDEF_PERCENT:{
4900         rrd_value_t *array;
4901         int       field;
4902         if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4903             rrd_set_error("malloc VDEV_PERCENT");
4904             return -1;
4905         }
4906         for (step = 0; step < steps; step++) {
4907             array[step] = data[step * src->ds_cnt];
4908         }
4909         qsort(array, step, sizeof(double), vdef_percent_compar);
4910         field = round((dst->vf.param * (double)(steps - 1)) / 100.0);
4911         dst->vf.val = array[field];
4912         dst->vf.when = 0;   /* no time component */
4913         free(array);
4914 #if 0
4915         for (step = 0; step < steps; step++)
4916             printf("DEBUG: %3li:%10.2f %c\n",
4917                    step, array[step], step == field ? '*' : ' ');
4918 #endif
4919     }
4920         break;
4921     case VDEF_PERCENTNAN:{
4922         rrd_value_t *array;
4923         int       field;
4924        /* count number of "valid" values */
4925        int nancount=0;
4926        for (step = 0; step < steps; step++) {
4927          if (!isnan(data[step * src->ds_cnt])) { nancount++; }
4928        }
4929        /* and allocate it */
4930         if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
4931             rrd_set_error("malloc VDEV_PERCENT");
4932             return -1;
4933         }
4934        /* and fill it in */
4935        field=0;
4936         for (step = 0; step < steps; step++) {
4937            if (!isnan(data[step * src->ds_cnt])) {
4938                 array[field] = data[step * src->ds_cnt];
4939                field++;
4940             }
4941         }
4942         qsort(array, nancount, sizeof(double), vdef_percent_compar);
4943         field = round( dst->vf.param * (double)(nancount - 1) / 100.0);
4944         dst->vf.val = array[field];
4945         dst->vf.when = 0;   /* no time component */
4946         free(array);
4947     }
4948         break;
4949     case VDEF_MAXIMUM:
4950         step = 0;
4951         while (step != steps && isnan(data[step * src->ds_cnt]))
4952             step++;
4953         if (step == steps) {
4954             dst->vf.val = DNAN;
4955             dst->vf.when = 0;
4956         } else {
4957             dst->vf.val = data[step * src->ds_cnt];
4958             dst->vf.when = src->start + (step + 1) * src->step;
4959         }
4960         while (step != steps) {
4961             if (finite(data[step * src->ds_cnt])) {
4962                 if (data[step * src->ds_cnt] > dst->vf.val) {
4963                     dst->vf.val = data[step * src->ds_cnt];
4964                     dst->vf.when = src->start + (step + 1) * src->step;
4965                 }
4966             }
4967             step++;
4968         }
4969         break;
4970     case VDEF_TOTAL:
4971     case VDEF_STDEV:
4972     case VDEF_AVERAGE:{
4973         int       cnt = 0;
4974         double    sum = 0.0;
4975         double    average = 0.0;
4976
4977         for (step = 0; step < steps; step++) {
4978             if (finite(data[step * src->ds_cnt])) {
4979                 sum += data[step * src->ds_cnt];
4980                 cnt++;
4981             };
4982         }
4983         if (cnt) {
4984             if (dst->vf.op == VDEF_TOTAL) {
4985                 dst->vf.val = sum * src->step;
4986                 dst->vf.when = 0;   /* no time component */
4987             } else if (dst->vf.op == VDEF_AVERAGE) {
4988                 dst->vf.val = sum / cnt;
4989                 dst->vf.when = 0;   /* no time component */
4990             } else {
4991                 average = sum / cnt;
4992                 sum = 0.0;
4993                 for (step = 0; step < steps; step++) {
4994                     if (finite(data[step * src->ds_cnt])) {
4995                         sum += pow((data[step * src->ds_cnt] - average), 2.0);
4996                     };
4997                 }
4998                 dst->vf.val = pow(sum / cnt, 0.5);
4999                 dst->vf.when = 0;   /* no time component */
5000             };
5001         } else {
5002             dst->vf.val = DNAN;
5003             dst->vf.when = 0;
5004         }
5005     }
5006         break;
5007     case VDEF_MINIMUM:
5008         step = 0;
5009         while (step != steps && isnan(data[step * src->ds_cnt]))
5010             step++;
5011         if (step == steps) {
5012             dst->vf.val = DNAN;
5013             dst->vf.when = 0;
5014         } else {
5015             dst->vf.val = data[step * src->ds_cnt];
5016             dst->vf.when = src->start + (step + 1) * src->step;
5017         }
5018         while (step != steps) {
5019             if (finite(data[step * src->ds_cnt])) {
5020                 if (data[step * src->ds_cnt] < dst->vf.val) {
5021                     dst->vf.val = data[step * src->ds_cnt];
5022                     dst->vf.when = src->start + (step + 1) * src->step;
5023                 }
5024             }
5025             step++;
5026         }
5027         break;
5028     case VDEF_FIRST:
5029         /* The time value returned here is one step before the
5030          * actual time value.  This is the start of the first
5031          * non-NaN interval.
5032          */
5033         step = 0;
5034         while (step != steps && isnan(data[step * src->ds_cnt]))
5035             step++;
5036         if (step == steps) {    /* all entries were NaN */
5037             dst->vf.val = DNAN;
5038             dst->vf.when = 0;
5039         } else {
5040             dst->vf.val = data[step * src->ds_cnt];
5041             dst->vf.when = src->start + step * src->step;
5042         }
5043         break;
5044     case VDEF_LAST:
5045         /* The time value returned here is the
5046          * actual time value.  This is the end of the last
5047          * non-NaN interval.
5048          */
5049         step = steps - 1;
5050         while (step >= 0 && isnan(data[step * src->ds_cnt]))
5051             step--;
5052         if (step < 0) { /* all entries were NaN */
5053             dst->vf.val = DNAN;
5054             dst->vf.when = 0;
5055         } else {
5056             dst->vf.val = data[step * src->ds_cnt];
5057             dst->vf.when = src->start + (step + 1) * src->step;
5058         }
5059         break;
5060     case VDEF_LSLSLOPE:
5061     case VDEF_LSLINT:
5062     case VDEF_LSLCORREL:{
5063         /* Bestfit line by linear least squares method */
5064
5065         int       cnt = 0;
5066         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5067
5068         SUMx = 0;
5069         SUMy = 0;
5070         SUMxy = 0;
5071         SUMxx = 0;
5072         SUMyy = 0;
5073         for (step = 0; step < steps; step++) {
5074             if (finite(data[step * src->ds_cnt])) {
5075                 cnt++;
5076                 SUMx += step;
5077                 SUMxx += step * step;
5078                 SUMxy += step * data[step * src->ds_cnt];
5079                 SUMy += data[step * src->ds_cnt];
5080                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5081             };
5082         }
5083
5084         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5085         y_intercept = (SUMy - slope * SUMx) / cnt;
5086         correl =
5087             (SUMxy -
5088              (SUMx * SUMy) / cnt) /
5089             sqrt((SUMxx -
5090                   (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5091         if (cnt) {
5092             if (dst->vf.op == VDEF_LSLSLOPE) {
5093                 dst->vf.val = slope;
5094                 dst->vf.when = 0;
5095             } else if (dst->vf.op == VDEF_LSLINT) {
5096                 dst->vf.val = y_intercept;
5097                 dst->vf.when = 0;
5098             } else if (dst->vf.op == VDEF_LSLCORREL) {
5099                 dst->vf.val = correl;
5100                 dst->vf.when = 0;
5101             };
5102         } else {
5103             dst->vf.val = DNAN;
5104             dst->vf.when = 0;
5105         }
5106     }
5107         break;
5108     }
5109     return 0;
5110 }
5111
5112 /* NaN < -INF < finite_values < INF */
5113 int vdef_percent_compar(
5114     const void
5115     *a,
5116     const void
5117     *b)
5118 {
5119     /* Equality is not returned; this doesn't hurt except
5120      * (maybe) for a little performance.
5121      */
5122
5123     /* First catch NaN values. They are smallest */
5124     if (isnan(*(double *) a))
5125         return -1;
5126     if (isnan(*(double *) b))
5127         return 1;
5128     /* NaN doesn't reach this part so INF and -INF are extremes.
5129      * The sign from isinf() is compatible with the sign we return
5130      */
5131     if (isinf(*(double *) a))
5132         return isinf(*(double *) a);
5133     if (isinf(*(double *) b))
5134         return isinf(*(double *) b);
5135     /* If we reach this, both values must be finite */
5136     if (*(double *) a < *(double *) b)
5137         return -1;
5138     else
5139         return 1;
5140 }
5141
5142 void grinfo_push(
5143     image_desc_t *im,
5144     char *key,
5145     rrd_info_type_t type,
5146     rrd_infoval_t value)
5147 {
5148     im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5149     if (im->grinfo == NULL) {
5150         im->grinfo = im->grinfo_current;
5151     }
5152 }