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