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