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