prepare for the release of rrdtool-1.3rc5
[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, leg_y = im->yimg;
1616     int       leg_y_prev = im->yimg;
1617     int       leg_cc;
1618     int       glue = 0;
1619     int       i, ii, mark = 0;
1620     char      prt_fctn; /*special printfunctions */
1621     char      default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1622     int      *legspace;
1623     char     *tab;
1624
1625     if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1626         if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1627             rrd_set_error("malloc for legspace");
1628             return -1;
1629         }
1630
1631         if (im->extra_flags & FULL_SIZE_MODE)
1632             leg_y = leg_y_prev =
1633                 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1634         for (i = 0; i < im->gdes_c; i++) {
1635             fill_last = fill;
1636             /* hide legends for rules which are not displayed */
1637             if (im->gdes[i].gf == GF_TEXTALIGN) {
1638                 default_txtalign = im->gdes[i].txtalign;
1639             }
1640
1641             if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1642                 if (im->gdes[i].gf == GF_HRULE
1643                     && (im->gdes[i].yrule <
1644                         im->minval || im->gdes[i].yrule > im->maxval))
1645                     im->gdes[i].legend[0] = '\0';
1646                 if (im->gdes[i].gf == GF_VRULE
1647                     && (im->gdes[i].xrule <
1648                         im->start || im->gdes[i].xrule > im->end))
1649                     im->gdes[i].legend[0] = '\0';
1650             }
1651
1652             /* turn \\t into tab */
1653             while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1654                 memmove(tab, tab + 1, strlen(tab));
1655                 tab[0] = (char) 9;
1656             }
1657             leg_cc = strlen(im->gdes[i].legend);
1658             /* is there a controle code ant the end of the legend string ? */
1659             if (leg_cc >= 2
1660                 && im->gdes[i].legend[leg_cc -
1661                                       2] == '\\' ) {
1662                 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1663                 leg_cc -= 2;
1664                 im->gdes[i].legend[leg_cc] = '\0';
1665             } else {
1666                 prt_fctn = '\0';
1667             }
1668             /* only valid control codes */
1669             if (prt_fctn != 'l' && prt_fctn != 'n' &&   /* a synonym for l */
1670                 prt_fctn != 'r' &&
1671                 prt_fctn != 'j' &&
1672                 prt_fctn != 'c' &&
1673                 prt_fctn != 's' &&
1674                 prt_fctn != '\0' && prt_fctn != 'g') {
1675                 free(legspace);
1676                 rrd_set_error
1677                     ("Unknown control code at the end of '%s\\%c'",
1678                      im->gdes[i].legend, prt_fctn);
1679                 return -1;
1680             }
1681             /* \n -> \l */
1682             if (prt_fctn == 'n') {
1683                 prt_fctn = 'l';
1684             }
1685
1686             /* remove exess space from the end of the legend for \g */
1687             while (prt_fctn == 'g' &&
1688                    leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1689                 leg_cc--;
1690                 im->gdes[i].legend[leg_cc] = '\0';
1691             }
1692
1693             if (leg_cc != 0) {
1694
1695                 /* no interleg space if string ends in \g */
1696                 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1697                 if (fill > 0) {
1698                     fill += legspace[i];
1699                 }
1700                 fill +=
1701                     gfx_get_text_width(im,
1702                                        fill + border,
1703                                        im->
1704                                        text_prop
1705                                        [TEXT_PROP_LEGEND].
1706                                        font,
1707                                        im->
1708                                        text_prop
1709                                        [TEXT_PROP_LEGEND].
1710                                        size,
1711                                        im->tabwidth, im->gdes[i].legend);
1712                 leg_c++;
1713             } else {
1714                 legspace[i] = 0;
1715             }
1716             /* who said there was a special tag ... ? */
1717             if (prt_fctn == 'g') {
1718                 prt_fctn = '\0';
1719             }
1720
1721             if (prt_fctn == '\0') {
1722                 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1723                     /* just one legend item is left right or center */
1724                     switch (default_txtalign) {
1725                     case TXA_RIGHT:
1726                         prt_fctn = 'r';
1727                         break;
1728                     case TXA_CENTER:
1729                         prt_fctn = 'c';
1730                         break;
1731                     case TXA_JUSTIFIED:
1732                         prt_fctn = 'j';
1733                         break;
1734                     default:
1735                         prt_fctn = 'l';
1736                         break;
1737                     }
1738                 }
1739                 /* is it time to place the legends ? */
1740                 if (fill > im->ximg - 2 * border) {
1741                     if (leg_c > 1) {
1742                         /* go back one */
1743                         i--;
1744                         fill = fill_last;
1745                         leg_c--;
1746                     }
1747                 }
1748                 if (leg_c == 1 && prt_fctn == 'j') {
1749                     prt_fctn = 'l';
1750                 }
1751             }
1752
1753
1754             if (prt_fctn != '\0') {
1755                 leg_x = border;
1756                 if (leg_c >= 2 && prt_fctn == 'j') {
1757                     glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1758                 } else {
1759                     glue = 0;
1760                 }
1761                 if (prt_fctn == 'c')
1762                     leg_x = (im->ximg - fill) / 2.0;
1763                 if (prt_fctn == 'r')
1764                     leg_x = im->ximg - fill - border;
1765                 for (ii = mark; ii <= i; ii++) {
1766                     if (im->gdes[ii].legend[0] == '\0')
1767                         continue;   /* skip empty legends */
1768                     im->gdes[ii].leg_x = leg_x;
1769                     im->gdes[ii].leg_y = leg_y;
1770                     leg_x +=
1771                         gfx_get_text_width(im, leg_x,
1772                                            im->
1773                                            text_prop
1774                                            [TEXT_PROP_LEGEND].
1775                                            font,
1776                                            im->
1777                                            text_prop
1778                                            [TEXT_PROP_LEGEND].
1779                                            size,
1780                                            im->tabwidth, im->gdes[ii].legend)
1781                         + legspace[ii]
1782                         + glue;
1783                 }
1784                 leg_y_prev = leg_y;
1785                 if (im->extra_flags & FULL_SIZE_MODE) {
1786                     /* only add y space if there was text on the line */
1787                     if (leg_x > border || prt_fctn == 's')
1788                         leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1789                     if (prt_fctn == 's')
1790                         leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1791                 } else {
1792                     if (leg_x > border || prt_fctn == 's')
1793                         leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1794                     if (prt_fctn == 's')
1795                         leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1796                 }
1797                 fill = 0;
1798                 leg_c = 0;
1799                 mark = ii;
1800             }
1801         }
1802
1803         if (im->extra_flags & FULL_SIZE_MODE) {
1804             if (leg_y != leg_y_prev) {
1805                 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1806                 im->yorigin =
1807                     leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1808             }
1809         } else {
1810             im->yimg = leg_y_prev;
1811             /* if we did place some legends we have to add vertical space */
1812             if (leg_y != im->yimg)
1813                 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1814         }
1815         free(legspace);
1816     }
1817     return 0;
1818 }
1819
1820 /* create a grid on the graph. it determines what to do
1821    from the values of xsize, start and end */
1822
1823 /* the xaxis labels are determined from the number of seconds per pixel
1824    in the requested graph */
1825
1826
1827
1828 int calc_horizontal_grid(
1829     image_desc_t
1830     *im)
1831 {
1832     double    range;
1833     double    scaledrange;
1834     int       pixel, i;
1835     int       gridind = 0;
1836     int       decimals, fractionals;
1837
1838     im->ygrid_scale.labfact = 2;
1839     range = im->maxval - im->minval;
1840     scaledrange = range / im->magfact;
1841     /* does the scale of this graph make it impossible to put lines
1842        on it? If so, give up. */
1843     if (isnan(scaledrange)) {
1844         return 0;
1845     }
1846
1847     /* find grid spaceing */
1848     pixel = 1;
1849     if (isnan(im->ygridstep)) {
1850         if (im->extra_flags & ALTYGRID) {
1851             /* find the value with max number of digits. Get number of digits */
1852             decimals =
1853                 ceil(log10
1854                      (max(fabs(im->maxval), fabs(im->minval)) *
1855                       im->viewfactor / im->magfact));
1856             if (decimals <= 0)  /* everything is small. make place for zero */
1857                 decimals = 1;
1858             im->ygrid_scale.gridstep =
1859                 pow((double) 10,
1860                     floor(log10(range * im->viewfactor / im->magfact))) /
1861                 im->viewfactor * im->magfact;
1862             if (im->ygrid_scale.gridstep == 0)  /* range is one -> 0.1 is reasonable scale */
1863                 im->ygrid_scale.gridstep = 0.1;
1864             /* should have at least 5 lines but no more then 15 */
1865             if (range / im->ygrid_scale.gridstep < 5)
1866                 im->ygrid_scale.gridstep /= 10;
1867             if (range / im->ygrid_scale.gridstep > 15)
1868                 im->ygrid_scale.gridstep *= 10;
1869             if (range / im->ygrid_scale.gridstep > 5) {
1870                 im->ygrid_scale.labfact = 1;
1871                 if (range / im->ygrid_scale.gridstep > 8)
1872                     im->ygrid_scale.labfact = 2;
1873             } else {
1874                 im->ygrid_scale.gridstep /= 5;
1875                 im->ygrid_scale.labfact = 5;
1876             }
1877             fractionals =
1878                 floor(log10
1879                       (im->ygrid_scale.gridstep *
1880                        (double) im->ygrid_scale.labfact * im->viewfactor /
1881                        im->magfact));
1882             if (fractionals < 0) {  /* small amplitude. */
1883                 int       len = decimals - fractionals + 1;
1884
1885                 if (im->unitslength < len + 2)
1886                     im->unitslength = len + 2;
1887                 sprintf(im->ygrid_scale.labfmt,
1888                         "%%%d.%df%s", len,
1889                         -fractionals, (im->symbol != ' ' ? " %c" : ""));
1890             } else {
1891                 int       len = decimals + 1;
1892
1893                 if (im->unitslength < len + 2)
1894                     im->unitslength = len + 2;
1895                 sprintf(im->ygrid_scale.labfmt,
1896                         "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1897             }
1898         } else {
1899             for (i = 0; ylab[i].grid > 0; i++) {
1900                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1901                 gridind = i;
1902                 if (pixel > 7)
1903                     break;
1904             }
1905
1906             for (i = 0; i < 4; i++) {
1907                 if (pixel * ylab[gridind].lfac[i] >=
1908                     2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1909                     im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1910                     break;
1911                 }
1912             }
1913
1914             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1915         }
1916     } else {
1917         im->ygrid_scale.gridstep = im->ygridstep;
1918         im->ygrid_scale.labfact = im->ylabfact;
1919     }
1920     return 1;
1921 }
1922
1923 int draw_horizontal_grid(
1924     image_desc_t
1925     *im)
1926 {
1927     int       i;
1928     double    scaledstep;
1929     char      graph_label[100];
1930     int       nlabels = 0;
1931     double    X0 = im->xorigin;
1932     double    X1 = im->xorigin + im->xsize;
1933     int       sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1934     int       egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1935     double    MaxY;
1936
1937     scaledstep =
1938         im->ygrid_scale.gridstep /
1939         (double) im->magfact * (double) im->viewfactor;
1940     MaxY = scaledstep * (double) egrid;
1941     for (i = sgrid; i <= egrid; i++) {
1942         double    Y0 = ytr(im,
1943                            im->ygrid_scale.gridstep * i);
1944         double    YN = ytr(im,
1945                            im->ygrid_scale.gridstep * (i + 1));
1946
1947         if (floor(Y0 + 0.5) >=
1948             im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1949             /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1950                with the chosen settings. Add a label if required by settings, or if
1951                there is only one label so far and the next grid line is out of bounds. */
1952             if (i % im->ygrid_scale.labfact == 0
1953                 || (nlabels == 1
1954                     && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1955                 if (im->symbol == ' ') {
1956                     if (im->extra_flags & ALTYGRID) {
1957                         sprintf(graph_label,
1958                                 im->ygrid_scale.labfmt,
1959                                 scaledstep * (double) i);
1960                     } else {
1961                         if (MaxY < 10) {
1962                             sprintf(graph_label, "%4.1f",
1963                                     scaledstep * (double) i);
1964                         } else {
1965                             sprintf(graph_label, "%4.0f",
1966                                     scaledstep * (double) i);
1967                         }
1968                     }
1969                 } else {
1970                     char      sisym = (i == 0 ? ' ' : im->symbol);
1971
1972                     if (im->extra_flags & ALTYGRID) {
1973                         sprintf(graph_label,
1974                                 im->ygrid_scale.labfmt,
1975                                 scaledstep * (double) i, sisym);
1976                     } else {
1977                         if (MaxY < 10) {
1978                             sprintf(graph_label, "%4.1f %c",
1979                                     scaledstep * (double) i, sisym);
1980                         } else {
1981                             sprintf(graph_label, "%4.0f %c",
1982                                     scaledstep * (double) i, sisym);
1983                         }
1984                     }
1985                 }
1986                 nlabels++;
1987                 gfx_text(im,
1988                          X0 -
1989                          im->
1990                          text_prop[TEXT_PROP_AXIS].
1991                          size, Y0,
1992                          im->graph_col[GRC_FONT],
1993                          im->
1994                          text_prop[TEXT_PROP_AXIS].
1995                          font,
1996                          im->
1997                          text_prop[TEXT_PROP_AXIS].
1998                          size, im->tabwidth, 0.0,
1999                          GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2000                 gfx_line(im, X0 - 2, Y0, X0, Y0,
2001                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2002                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2003                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2004                 gfx_dashed_line(im, X0 - 2, Y0,
2005                                 X1 + 2, Y0,
2006                                 MGRIDWIDTH,
2007                                 im->
2008                                 graph_col
2009                                 [GRC_MGRID],
2010                                 im->grid_dash_on, im->grid_dash_off);
2011             } else if (!(im->extra_flags & NOMINOR)) {
2012                 gfx_line(im,
2013                          X0 - 2, Y0,
2014                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2015                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2016                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2017                 gfx_dashed_line(im, X0 - 1, Y0,
2018                                 X1 + 1, Y0,
2019                                 GRIDWIDTH,
2020                                 im->
2021                                 graph_col[GRC_GRID],
2022                                 im->grid_dash_on, im->grid_dash_off);
2023             }
2024         }
2025     }
2026     return 1;
2027 }
2028
2029 /* this is frexp for base 10 */
2030 double    frexp10(
2031     double,
2032     double *);
2033 double frexp10(
2034     double x,
2035     double *e)
2036 {
2037     double    mnt;
2038     int       iexp;
2039
2040     iexp = floor(log(fabs(x)) / log(10));
2041     mnt = x / pow(10.0, iexp);
2042     if (mnt >= 10.0) {
2043         iexp++;
2044         mnt = x / pow(10.0, iexp);
2045     }
2046     *e = iexp;
2047     return mnt;
2048 }
2049
2050
2051 /* logaritmic horizontal grid */
2052 int horizontal_log_grid(
2053     image_desc_t
2054     *im)
2055 {
2056     double    yloglab[][10] = {
2057         {
2058          1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2059          0.0, 0.0, 0.0}, {
2060                           1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2061                           0.0, 0.0, 0.0}, {
2062                                            1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2063                                            0.0, 0.0, 0.0}, {
2064                                                             1.0, 2.0, 4.0,
2065                                                             6.0, 8.0, 10.,
2066                                                             0.0,
2067                                                             0.0, 0.0, 0.0}, {
2068                                                                              1.0,
2069                                                                              2.0,
2070                                                                              3.0,
2071                                                                              4.0,
2072                                                                              5.0,
2073                                                                              6.0,
2074                                                                              7.0,
2075                                                                              8.0,
2076                                                                              9.0,
2077                                                                              10.},
2078         {
2079          0, 0, 0, 0, 0, 0, 0, 0, 0, 0}  /* last line */
2080     };
2081     int       i, j, val_exp, min_exp;
2082     double    nex;      /* number of decades in data */
2083     double    logscale; /* scale in logarithmic space */
2084     int       exfrac = 1;   /* decade spacing */
2085     int       mid = -1; /* row in yloglab for major grid */
2086     double    mspac;    /* smallest major grid spacing (pixels) */
2087     int       flab;     /* first value in yloglab to use */
2088     double    value, tmp, pre_value;
2089     double    X0, X1, Y0;
2090     char      graph_label[100];
2091
2092     nex = log10(im->maxval / im->minval);
2093     logscale = im->ysize / nex;
2094     /* major spacing for data with high dynamic range */
2095     while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2096         if (exfrac == 1)
2097             exfrac = 3;
2098         else
2099             exfrac += 3;
2100     }
2101
2102     /* major spacing for less dynamic data */
2103     do {
2104         /* search best row in yloglab */
2105         mid++;
2106         for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2107         mspac = logscale * log10(10.0 / yloglab[mid][i]);
2108     }
2109     while (mspac >
2110            2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2111     if (mid)
2112         mid--;
2113     /* find first value in yloglab */
2114     for (flab = 0;
2115          yloglab[mid][flab] < 10
2116          && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2117     if (yloglab[mid][flab] == 10.0) {
2118         tmp += 1.0;
2119         flab = 0;
2120     }
2121     val_exp = tmp;
2122     if (val_exp % exfrac)
2123         val_exp += abs(-val_exp % exfrac);
2124     X0 = im->xorigin;
2125     X1 = im->xorigin + im->xsize;
2126     /* draw grid */
2127     pre_value = DNAN;
2128     while (1) {
2129
2130         value = yloglab[mid][flab] * pow(10.0, val_exp);
2131         if (AlmostEqual2sComplement(value, pre_value, 4))
2132             break;      /* it seems we are not converging */
2133         pre_value = value;
2134         Y0 = ytr(im, value);
2135         if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2136             break;
2137         /* major grid line */
2138         gfx_line(im,
2139                  X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2140         gfx_line(im, X1, Y0, X1 + 2, Y0,
2141                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2142         gfx_dashed_line(im, X0 - 2, Y0,
2143                         X1 + 2, Y0,
2144                         MGRIDWIDTH,
2145                         im->
2146                         graph_col
2147                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2148         /* label */
2149         if (im->extra_flags & FORCE_UNITS_SI) {
2150             int       scale;
2151             double    pvalue;
2152             char      symbol;
2153
2154             scale = floor(val_exp / 3.0);
2155             if (value >= 1.0)
2156                 pvalue = pow(10.0, val_exp % 3);
2157             else
2158                 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2159             pvalue *= yloglab[mid][flab];
2160             if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2161                 && ((scale + si_symbcenter) >= 0))
2162                 symbol = si_symbol[scale + si_symbcenter];
2163             else
2164                 symbol = '?';
2165             sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2166         } else
2167             sprintf(graph_label, "%3.0e", value);
2168         gfx_text(im,
2169                  X0 -
2170                  im->
2171                  text_prop[TEXT_PROP_AXIS].
2172                  size, Y0,
2173                  im->graph_col[GRC_FONT],
2174                  im->
2175                  text_prop[TEXT_PROP_AXIS].
2176                  font,
2177                  im->
2178                  text_prop[TEXT_PROP_AXIS].
2179                  size, im->tabwidth, 0.0,
2180                  GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2181         /* minor grid */
2182         if (mid < 4 && exfrac == 1) {
2183             /* find first and last minor line behind current major line
2184              * i is the first line and j tha last */
2185             if (flab == 0) {
2186                 min_exp = val_exp - 1;
2187                 for (i = 1; yloglab[mid][i] < 10.0; i++);
2188                 i = yloglab[mid][i - 1] + 1;
2189                 j = 10;
2190             } else {
2191                 min_exp = val_exp;
2192                 i = yloglab[mid][flab - 1] + 1;
2193                 j = yloglab[mid][flab];
2194             }
2195
2196             /* draw minor lines below current major line */
2197             for (; i < j; i++) {
2198
2199                 value = i * pow(10.0, min_exp);
2200                 if (value < im->minval)
2201                     continue;
2202                 Y0 = ytr(im, value);
2203                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2204                     break;
2205                 /* draw lines */
2206                 gfx_line(im,
2207                          X0 - 2, Y0,
2208                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2209                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2210                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2211                 gfx_dashed_line(im, X0 - 1, Y0,
2212                                 X1 + 1, Y0,
2213                                 GRIDWIDTH,
2214                                 im->
2215                                 graph_col[GRC_GRID],
2216                                 im->grid_dash_on, im->grid_dash_off);
2217             }
2218         } else if (exfrac > 1) {
2219             for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2220                 value = pow(10.0, i);
2221                 if (value < im->minval)
2222                     continue;
2223                 Y0 = ytr(im, value);
2224                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2225                     break;
2226                 /* draw lines */
2227                 gfx_line(im,
2228                          X0 - 2, Y0,
2229                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2230                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2231                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2232                 gfx_dashed_line(im, X0 - 1, Y0,
2233                                 X1 + 1, Y0,
2234                                 GRIDWIDTH,
2235                                 im->
2236                                 graph_col[GRC_GRID],
2237                                 im->grid_dash_on, im->grid_dash_off);
2238             }
2239         }
2240
2241         /* next decade */
2242         if (yloglab[mid][++flab] == 10.0) {
2243             flab = 0;
2244             val_exp += exfrac;
2245         }
2246     }
2247
2248     /* draw minor lines after highest major line */
2249     if (mid < 4 && exfrac == 1) {
2250         /* find first and last minor line below current major line
2251          * i is the first line and j tha last */
2252         if (flab == 0) {
2253             min_exp = val_exp - 1;
2254             for (i = 1; yloglab[mid][i] < 10.0; i++);
2255             i = yloglab[mid][i - 1] + 1;
2256             j = 10;
2257         } else {
2258             min_exp = val_exp;
2259             i = yloglab[mid][flab - 1] + 1;
2260             j = yloglab[mid][flab];
2261         }
2262
2263         /* draw minor lines below current major line */
2264         for (; i < j; i++) {
2265
2266             value = i * pow(10.0, min_exp);
2267             if (value < im->minval)
2268                 continue;
2269             Y0 = ytr(im, value);
2270             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2271                 break;
2272             /* draw lines */
2273             gfx_line(im,
2274                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2275             gfx_line(im, X1, Y0, X1 + 2, Y0,
2276                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2277             gfx_dashed_line(im, X0 - 1, Y0,
2278                             X1 + 1, Y0,
2279                             GRIDWIDTH,
2280                             im->
2281                             graph_col[GRC_GRID],
2282                             im->grid_dash_on, im->grid_dash_off);
2283         }
2284     }
2285     /* fancy minor gridlines */
2286     else if (exfrac > 1) {
2287         for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2288             value = pow(10.0, i);
2289             if (value < im->minval)
2290                 continue;
2291             Y0 = ytr(im, value);
2292             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2293                 break;
2294             /* draw lines */
2295             gfx_line(im,
2296                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2297             gfx_line(im, X1, Y0, X1 + 2, Y0,
2298                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2299             gfx_dashed_line(im, X0 - 1, Y0,
2300                             X1 + 1, Y0,
2301                             GRIDWIDTH,
2302                             im->
2303                             graph_col[GRC_GRID],
2304                             im->grid_dash_on, im->grid_dash_off);
2305         }
2306     }
2307
2308     return 1;
2309 }
2310
2311
2312 void vertical_grid(
2313     image_desc_t *im)
2314 {
2315     int       xlab_sel; /* which sort of label and grid ? */
2316     time_t    ti, tilab, timajor;
2317     long      factor;
2318     char      graph_label[100];
2319     double    X0, Y0, Y1;   /* points for filled graph and more */
2320     struct tm tm;
2321
2322     /* the type of time grid is determined by finding
2323        the number of seconds per pixel in the graph */
2324     if (im->xlab_user.minsec == -1) {
2325         factor = (im->end - im->start) / im->xsize;
2326         xlab_sel = 0;
2327         while (xlab[xlab_sel + 1].minsec !=
2328                -1 && xlab[xlab_sel + 1].minsec <= factor) {
2329             xlab_sel++;
2330         }               /* pick the last one */
2331         while (xlab[xlab_sel - 1].minsec ==
2332                xlab[xlab_sel].minsec
2333                && xlab[xlab_sel].length > (im->end - im->start)) {
2334             xlab_sel--;
2335         }               /* go back to the smallest size */
2336         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2337         im->xlab_user.gridst = xlab[xlab_sel].gridst;
2338         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2339         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2340         im->xlab_user.labtm = xlab[xlab_sel].labtm;
2341         im->xlab_user.labst = xlab[xlab_sel].labst;
2342         im->xlab_user.precis = xlab[xlab_sel].precis;
2343         im->xlab_user.stst = xlab[xlab_sel].stst;
2344     }
2345
2346     /* y coords are the same for every line ... */
2347     Y0 = im->yorigin;
2348     Y1 = im->yorigin - im->ysize;
2349     /* paint the minor grid */
2350     if (!(im->extra_flags & NOMINOR)) {
2351         for (ti = find_first_time(im->start,
2352                                   im->
2353                                   xlab_user.
2354                                   gridtm,
2355                                   im->
2356                                   xlab_user.
2357                                   gridst),
2358              timajor =
2359              find_first_time(im->start,
2360                              im->xlab_user.
2361                              mgridtm,
2362                              im->xlab_user.
2363                              mgridst);
2364              ti < im->end;
2365              ti =
2366              find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2367             ) {
2368             /* are we inside the graph ? */
2369             if (ti < im->start || ti > im->end)
2370                 continue;
2371             while (timajor < ti) {
2372                 timajor = find_next_time(timajor,
2373                                          im->
2374                                          xlab_user.
2375                                          mgridtm, im->xlab_user.mgridst);
2376             }
2377             if (ti == timajor)
2378                 continue;   /* skip as falls on major grid line */
2379             X0 = xtr(im, ti);
2380             gfx_line(im, X0, Y1 - 2, X0, Y1,
2381                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2382             gfx_line(im, X0, Y0, X0, Y0 + 2,
2383                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2384             gfx_dashed_line(im, X0, Y0 + 1, X0,
2385                             Y1 - 1, GRIDWIDTH,
2386                             im->
2387                             graph_col[GRC_GRID],
2388                             im->grid_dash_on, im->grid_dash_off);
2389         }
2390     }
2391
2392     /* paint the major grid */
2393     for (ti = find_first_time(im->start,
2394                               im->
2395                               xlab_user.
2396                               mgridtm,
2397                               im->
2398                               xlab_user.
2399                               mgridst);
2400          ti < im->end;
2401          ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2402         ) {
2403         /* are we inside the graph ? */
2404         if (ti < im->start || ti > im->end)
2405             continue;
2406         X0 = xtr(im, ti);
2407         gfx_line(im, X0, Y1 - 2, X0, Y1,
2408                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2409         gfx_line(im, X0, Y0, X0, Y0 + 3,
2410                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2411         gfx_dashed_line(im, X0, Y0 + 3, X0,
2412                         Y1 - 2, MGRIDWIDTH,
2413                         im->
2414                         graph_col
2415                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2416     }
2417     /* paint the labels below the graph */
2418     for (ti =
2419          find_first_time(im->start -
2420                          im->xlab_user.
2421                          precis / 2,
2422                          im->xlab_user.
2423                          labtm,
2424                          im->xlab_user.
2425                          labst);
2426          ti <=
2427          im->end -
2428          im->xlab_user.precis / 2;
2429          ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2430         ) {
2431         tilab = ti + im->xlab_user.precis / 2;  /* correct time for the label */
2432         /* are we inside the graph ? */
2433         if (tilab < im->start || tilab > im->end)
2434             continue;
2435 #if HAVE_STRFTIME
2436         localtime_r(&tilab, &tm);
2437         strftime(graph_label, 99, im->xlab_user.stst, &tm);
2438 #else
2439 # error "your libc has no strftime I guess we'll abort the exercise here."
2440 #endif
2441         gfx_text(im,
2442                  xtr(im, tilab),
2443                  Y0 + 3,
2444                  im->graph_col[GRC_FONT],
2445                  im->
2446                  text_prop[TEXT_PROP_AXIS].
2447                  font,
2448                  im->
2449                  text_prop[TEXT_PROP_AXIS].
2450                  size, im->tabwidth, 0.0,
2451                  GFX_H_CENTER, GFX_V_TOP, graph_label);
2452     }
2453
2454 }
2455
2456
2457 void axis_paint(
2458     image_desc_t *im)
2459 {
2460     /* draw x and y axis */
2461     /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2462        im->xorigin+im->xsize,im->yorigin-im->ysize,
2463        GRIDWIDTH, im->graph_col[GRC_AXIS]);
2464
2465        gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2466        im->xorigin+im->xsize,im->yorigin-im->ysize,
2467        GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2468
2469     gfx_line(im, im->xorigin - 4,
2470              im->yorigin,
2471              im->xorigin + im->xsize +
2472              4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2473     gfx_line(im, im->xorigin,
2474              im->yorigin + 4,
2475              im->xorigin,
2476              im->yorigin - im->ysize -
2477              4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2478     /* arrow for X and Y axis direction */
2479     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 */
2480                  im->graph_col[GRC_ARROW]);
2481     gfx_close_path(im);
2482     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 */
2483                  im->graph_col[GRC_ARROW]);
2484     gfx_close_path(im);
2485 }
2486
2487 void grid_paint(
2488     image_desc_t *im)
2489 {
2490     long      i;
2491     int       res = 0;
2492     double    X0, Y0;   /* points for filled graph and more */
2493     struct gfx_color_t water_color;
2494
2495     /* draw 3d border */
2496     gfx_new_area(im, 0, im->yimg,
2497                  2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2498     gfx_add_point(im, im->ximg - 2, 2);
2499     gfx_add_point(im, im->ximg, 0);
2500     gfx_add_point(im, 0, 0);
2501     gfx_close_path(im);
2502     gfx_new_area(im, 2, im->yimg - 2,
2503                  im->ximg - 2,
2504                  im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2505     gfx_add_point(im, im->ximg, 0);
2506     gfx_add_point(im, im->ximg, im->yimg);
2507     gfx_add_point(im, 0, im->yimg);
2508     gfx_close_path(im);
2509     if (im->draw_x_grid == 1)
2510         vertical_grid(im);
2511     if (im->draw_y_grid == 1) {
2512         if (im->logarithmic) {
2513             res = horizontal_log_grid(im);
2514         } else {
2515             res = draw_horizontal_grid(im);
2516         }
2517
2518         /* dont draw horizontal grid if there is no min and max val */
2519         if (!res) {
2520             char     *nodata = "No Data found";
2521
2522             gfx_text(im, im->ximg / 2,
2523                      (2 * im->yorigin -
2524                       im->ysize) / 2,
2525                      im->graph_col[GRC_FONT],
2526                      im->
2527                      text_prop[TEXT_PROP_AXIS].
2528                      font,
2529                      im->
2530                      text_prop[TEXT_PROP_AXIS].
2531                      size, im->tabwidth, 0.0,
2532                      GFX_H_CENTER, GFX_V_CENTER, nodata);
2533         }
2534     }
2535
2536     /* yaxis unit description */
2537     gfx_text(im,
2538              10,
2539              (im->yorigin -
2540               im->ysize / 2),
2541              im->graph_col[GRC_FONT],
2542              im->
2543              text_prop[TEXT_PROP_UNIT].
2544              font,
2545              im->
2546              text_prop[TEXT_PROP_UNIT].
2547              size, im->tabwidth,
2548              RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2549     /* graph title */
2550     gfx_text(im,
2551              im->ximg / 2, 6,
2552              im->graph_col[GRC_FONT],
2553              im->
2554              text_prop[TEXT_PROP_TITLE].
2555              font,
2556              im->
2557              text_prop[TEXT_PROP_TITLE].
2558              size, im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2559     /* rrdtool 'logo' */
2560     water_color = im->graph_col[GRC_FONT];
2561     water_color.alpha = 0.3;
2562     gfx_text(im, im->ximg - 4, 5,
2563              water_color,
2564              im->
2565              text_prop[TEXT_PROP_AXIS].
2566              font, 5.5, im->tabwidth,
2567              -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2568     /* graph watermark */
2569     if (im->watermark[0] != '\0') {
2570         gfx_text(im,
2571                  im->ximg / 2, im->yimg - 6,
2572                  water_color,
2573                  im->
2574                  text_prop[TEXT_PROP_AXIS].
2575                  font, 5.5, im->tabwidth, 0,
2576                  GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2577     }
2578
2579     /* graph labels */
2580     if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2581         for (i = 0; i < im->gdes_c; i++) {
2582             if (im->gdes[i].legend[0] == '\0')
2583                 continue;
2584             /* im->gdes[i].leg_y is the bottom of the legend */
2585             X0 = im->gdes[i].leg_x;
2586             Y0 = im->gdes[i].leg_y;
2587             gfx_text(im, X0, Y0,
2588                      im->graph_col[GRC_FONT],
2589                      im->
2590                      text_prop
2591                      [TEXT_PROP_LEGEND].font,
2592                      im->
2593                      text_prop
2594                      [TEXT_PROP_LEGEND].size,
2595                      im->tabwidth, 0.0,
2596                      GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2597             /* The legend for GRAPH items starts with "M " to have
2598                enough space for the box */
2599             if (im->gdes[i].gf != GF_PRINT &&
2600                 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2601                 double    boxH, boxV;
2602                 double    X1, Y1;
2603
2604                 boxH = gfx_get_text_width(im, 0,
2605                                           im->
2606                                           text_prop
2607                                           [TEXT_PROP_LEGEND].
2608                                           font,
2609                                           im->
2610                                           text_prop
2611                                           [TEXT_PROP_LEGEND].
2612                                           size, im->tabwidth, "o") * 1.2;
2613                 boxV = boxH;
2614                 /* shift the box up a bit */
2615                 Y0 -= boxV * 0.4;
2616                 /* make sure transparent colors show up the same way as in the graph */
2617                 gfx_new_area(im,
2618                              X0, Y0 - boxV,
2619                              X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2620                 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2621                 gfx_close_path(im);
2622                 gfx_new_area(im, X0, Y0 - boxV, X0,
2623                              Y0, X0 + boxH, Y0, im->gdes[i].col);
2624                 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2625                 gfx_close_path(im);
2626                 cairo_save(im->cr);
2627                 cairo_new_path(im->cr);
2628                 cairo_set_line_width(im->cr, 1.0);
2629                 X1 = X0 + boxH;
2630                 Y1 = Y0 - boxV;
2631                 gfx_line_fit(im, &X0, &Y0);
2632                 gfx_line_fit(im, &X1, &Y1);
2633                 cairo_move_to(im->cr, X0, Y0);
2634                 cairo_line_to(im->cr, X1, Y0);
2635                 cairo_line_to(im->cr, X1, Y1);
2636                 cairo_line_to(im->cr, X0, Y1);
2637                 cairo_close_path(im->cr);
2638                 cairo_set_source_rgba(im->cr,
2639                                       im->
2640                                       graph_col
2641                                       [GRC_FRAME].
2642                                       red,
2643                                       im->
2644                                       graph_col
2645                                       [GRC_FRAME].
2646                                       green,
2647                                       im->
2648                                       graph_col
2649                                       [GRC_FRAME].
2650                                       blue, im->graph_col[GRC_FRAME].alpha);
2651                 if (im->gdes[i].dash) {
2652                     /* make box borders in legend dashed if the graph is dashed */
2653                     double    dashes[] = {
2654                         3.0
2655                     };
2656                     cairo_set_dash(im->cr, dashes, 1, 0.0);
2657                 }
2658                 cairo_stroke(im->cr);
2659                 cairo_restore(im->cr);
2660             }
2661         }
2662     }
2663 }
2664
2665
2666 /*****************************************************
2667  * lazy check make sure we rely need to create this graph
2668  *****************************************************/
2669
2670 int lazy_check(
2671     image_desc_t *im)
2672 {
2673     FILE     *fd = NULL;
2674     int       size = 1;
2675     struct stat imgstat;
2676
2677     if (im->lazy == 0)
2678         return 0;       /* no lazy option */
2679     if (strlen(im->graphfile) == 0)
2680         return 0;       /* inmemory option */
2681     if (stat(im->graphfile, &imgstat) != 0)
2682         return 0;       /* can't stat */
2683     /* one pixel in the existing graph is more then what we would
2684        change here ... */
2685     if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2686         return 0;
2687     if ((fd = fopen(im->graphfile, "rb")) == NULL)
2688         return 0;       /* the file does not exist */
2689     switch (im->imgformat) {
2690     case IF_PNG:
2691         size = PngSize(fd, &(im->ximg), &(im->yimg));
2692         break;
2693     default:
2694         size = 1;
2695     }
2696     fclose(fd);
2697     return size;
2698 }
2699
2700
2701 int graph_size_location(
2702     image_desc_t
2703     *im,
2704     int elements)
2705 {
2706     /* The actual size of the image to draw is determined from
2707      ** several sources.  The size given on the command line is
2708      ** the graph area but we need more as we have to draw labels
2709      ** and other things outside the graph area
2710      */
2711
2712     int       Xvertical = 0, Ytitle =
2713         0, Xylabel = 0, Xmain = 0, Ymain =
2714         0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2715
2716     if (im->extra_flags & ONLY_GRAPH) {
2717         im->xorigin = 0;
2718         im->ximg = im->xsize;
2719         im->yimg = im->ysize;
2720         im->yorigin = im->ysize;
2721         ytr(im, DNAN);
2722         return 0;
2723     }
2724
2725     /** +---+--------------------------------------------+
2726      ** | y |...............graph title..................|
2727      ** |   +---+-------------------------------+--------+
2728      ** | a | y |                               |        |
2729      ** | x |   |                               |        |
2730      ** | i | a |                               |  pie   |
2731      ** | s | x |       main graph area         | chart  |
2732      ** |   | i |                               |  area  |
2733      ** | t | s |                               |        |
2734      ** | i |   |                               |        |
2735      ** | t | l |                               |        |
2736      ** | l | b +-------------------------------+--------+
2737      ** | e | l |       x axis labels           |        |
2738      ** +---+---+-------------------------------+--------+
2739      ** |....................legends.....................|
2740      ** +------------------------------------------------+
2741      ** |                   watermark                    |
2742      ** +------------------------------------------------+
2743      */
2744
2745     if (im->ylegend[0] != '\0') {
2746         Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2747     }
2748
2749     if (im->title[0] != '\0') {
2750         /* The title is placed "inbetween" two text lines so it
2751          ** automatically has some vertical spacing.  The horizontal
2752          ** spacing is added here, on each side.
2753          */
2754         /* if necessary, reduce the font size of the title until it fits the image width */
2755         Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2756     }
2757
2758     if (elements) {
2759         if (im->draw_x_grid) {
2760             Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2761         }
2762         if (im->draw_y_grid || im->forceleftspace) {
2763             Xylabel =
2764                 gfx_get_text_width(im, 0,
2765                                    im->
2766                                    text_prop
2767                                    [TEXT_PROP_AXIS].
2768                                    font,
2769                                    im->
2770                                    text_prop
2771                                    [TEXT_PROP_AXIS].
2772                                    size, im->tabwidth, "0") * im->unitslength;
2773         }
2774     }
2775
2776     if (im->extra_flags & FULL_SIZE_MODE) {
2777         /* The actual size of the image to draw has been determined by the user.
2778          ** The graph area is the space remaining after accounting for the legend,
2779          ** the watermark, the pie chart, the axis labels, and the title.
2780          */
2781         im->xorigin = 0;
2782         im->ximg = im->xsize;
2783         im->yimg = im->ysize;
2784         im->yorigin = im->ysize;
2785         Xmain = im->ximg;
2786         Ymain = im->yimg;
2787         im->yorigin += Ytitle;
2788         /* Now calculate the total size.  Insert some spacing where
2789            desired.  im->xorigin and im->yorigin need to correspond
2790            with the lower left corner of the main graph area or, if
2791            this one is not set, the imaginary box surrounding the
2792            pie chart area. */
2793         /* Initial size calculation for the main graph area */
2794         Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2795         if (Xmain)
2796             Xmain -= Xspacing;  /* put space between main graph area and right edge */
2797         im->xorigin = Xspacing + Xylabel;
2798         /* the length of the title should not influence with width of the graph
2799            if (Xtitle > im->ximg) im->ximg = Xtitle; */
2800         if (Xvertical) {    /* unit description */
2801             Xmain -= Xvertical;
2802             im->xorigin += Xvertical;
2803         }
2804         im->xsize = Xmain;
2805         xtr(im, 0);
2806         /* The vertical size of the image is known in advance.  The main graph area
2807          ** (Ymain) and im->yorigin must be set according to the space requirements
2808          ** of the legend and the axis labels.
2809          */
2810         if (im->extra_flags & NOLEGEND) {
2811             /* set dimensions correctly if using full size mode with no legend */
2812             im->yorigin =
2813                 im->yimg -
2814                 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2815             Ymain = im->yorigin;
2816         } else {
2817             /* Determine where to place the legends onto the image.
2818              ** Set Ymain and adjust im->yorigin to match the space requirements.
2819              */
2820             if (leg_place(im, &Ymain) == -1)
2821                 return -1;
2822         }
2823
2824
2825         /* remove title space *or* some padding above the graph from the main graph area */
2826         if (Ytitle) {
2827             Ymain -= Ytitle;
2828         } else {
2829             Ymain -= 1.5 * Yspacing;
2830         }
2831
2832         /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2833         if (im->watermark[0] != '\0') {
2834             Ymain -= Ywatermark;
2835         }
2836
2837         im->ysize = Ymain;
2838     } else {            /* dimension options -width and -height refer to the dimensions of the main graph area */
2839
2840         /* The actual size of the image to draw is determined from
2841          ** several sources.  The size given on the command line is
2842          ** the graph area but we need more as we have to draw labels
2843          ** and other things outside the graph area.
2844          */
2845
2846         if (im->ylegend[0] != '\0') {
2847             Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2848         }
2849
2850
2851         if (im->title[0] != '\0') {
2852             /* The title is placed "inbetween" two text lines so it
2853              ** automatically has some vertical spacing.  The horizontal
2854              ** spacing is added here, on each side.
2855              */
2856             /* don't care for the with of the title
2857                Xtitle = gfx_get_text_width(im->canvas, 0,
2858                im->text_prop[TEXT_PROP_TITLE].font,
2859                im->text_prop[TEXT_PROP_TITLE].size,
2860                im->tabwidth,
2861                im->title, 0) + 2*Xspacing; */
2862             Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2863         }
2864
2865         if (elements) {
2866             Xmain = im->xsize;
2867             Ymain = im->ysize;
2868         }
2869         /* Now calculate the total size.  Insert some spacing where
2870            desired.  im->xorigin and im->yorigin need to correspond
2871            with the lower left corner of the main graph area or, if
2872            this one is not set, the imaginary box surrounding the
2873            pie chart area. */
2874
2875         /* The legend width cannot yet be determined, as a result we
2876          ** have problems adjusting the image to it.  For now, we just
2877          ** forget about it at all; the legend will have to fit in the
2878          ** size already allocated.
2879          */
2880         im->ximg = Xylabel + Xmain + 2 * Xspacing;
2881         if (Xmain)
2882             im->ximg += Xspacing;
2883         im->xorigin = Xspacing + Xylabel;
2884         /* the length of the title should not influence with width of the graph
2885            if (Xtitle > im->ximg) im->ximg = Xtitle; */
2886         if (Xvertical) {    /* unit description */
2887             im->ximg += Xvertical;
2888             im->xorigin += Xvertical;
2889         }
2890         xtr(im, 0);
2891         /* The vertical size is interesting... we need to compare
2892          ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with 
2893          ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2894          ** in order to start even thinking about Ylegend or Ywatermark.
2895          **
2896          ** Do it in three portions: First calculate the inner part,
2897          ** then do the legend, then adjust the total height of the img,
2898          ** adding space for a watermark if one exists;
2899          */
2900         /* reserve space for main and/or pie */
2901         im->yimg = Ymain + Yxlabel;
2902         im->yorigin = im->yimg - Yxlabel;
2903         /* reserve space for the title *or* some padding above the graph */
2904         if (Ytitle) {
2905             im->yimg += Ytitle;
2906             im->yorigin += Ytitle;
2907         } else {
2908             im->yimg += 1.5 * Yspacing;
2909             im->yorigin += 1.5 * Yspacing;
2910         }
2911         /* reserve space for padding below the graph */
2912         im->yimg += Yspacing;
2913         /* Determine where to place the legends onto the image.
2914          ** Adjust im->yimg to match the space requirements.
2915          */
2916         if (leg_place(im, 0) == -1)
2917             return -1;
2918         if (im->watermark[0] != '\0') {
2919             im->yimg += Ywatermark;
2920         }
2921     }
2922
2923     ytr(im, DNAN);
2924     return 0;
2925 }
2926
2927 static cairo_status_t cairo_output(
2928     void *closure,
2929     const unsigned char
2930     *data,
2931     unsigned int length)
2932 {
2933     image_desc_t *im = closure;
2934
2935     im->rendered_image =
2936         realloc(im->rendered_image, im->rendered_image_size + length);
2937     if (im->rendered_image == NULL)
2938         return CAIRO_STATUS_WRITE_ERROR;
2939     memcpy(im->rendered_image + im->rendered_image_size, data, length);
2940     im->rendered_image_size += length;
2941     return CAIRO_STATUS_SUCCESS;
2942 }
2943
2944 /* draw that picture thing ... */
2945 int graph_paint(
2946     image_desc_t *im)
2947 {
2948     int       i, ii;
2949     int       lazy = lazy_check(im);
2950     double    areazero = 0.0;
2951     graph_desc_t *lastgdes = NULL;
2952     infoval   info;
2953     PangoFontMap *font_map = pango_cairo_font_map_get_default();
2954
2955     /* if we are lazy and there is nothing to PRINT ... quit now */
2956     if (lazy && im->prt_c == 0)
2957         return 0;
2958     /* pull the data from the rrd files ... */
2959     if (data_fetch(im) == -1)
2960         return -1;
2961     /* evaluate VDEF and CDEF operations ... */
2962     if (data_calc(im) == -1)
2963         return -1;
2964     /* calculate and PRINT and GPRINT definitions. We have to do it at
2965      * this point because it will affect the length of the legends
2966      * if there are no graph elements we stop here ... 
2967      * if we are lazy, try to quit ... 
2968      */
2969     i = print_calc(im);
2970     if (i < 0)
2971         return -1;
2972     if ((i == 0) || lazy)
2973         return 0;
2974 /**************************************************************
2975  *** Calculating sizes and locations became a bit confusing ***
2976  *** so I moved this into a separate function.              ***
2977  **************************************************************/
2978     if (graph_size_location(im, i) == -1)
2979         return -1;
2980
2981     info.u_cnt = im->xorigin;
2982     grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
2983     info.u_cnt = im->yorigin - im->ysize;
2984     grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
2985     info.u_cnt = im->xsize;
2986     grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
2987     info.u_cnt = im->ysize;
2988     grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
2989     info.u_cnt = im->ximg;
2990     grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2991     info.u_cnt = im->yimg;
2992     grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
2993
2994     /* get actual drawing data and find min and max values */
2995     if (data_proc(im) == -1)
2996         return -1;
2997     if (!im->logarithmic) {
2998         si_unit(im);
2999     }
3000
3001     /* identify si magnitude Kilo, Mega Giga ? */
3002     if (!im->rigid && !im->logarithmic)
3003         expand_range(im);   /* make sure the upper and lower limit are
3004                                sensible values */
3005
3006     info.u_val = im->minval;
3007     grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3008     info.u_val = im->maxval;
3009     grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3010
3011     if (!calc_horizontal_grid(im))
3012         return -1;
3013     /* reset precalc */
3014     ytr(im, DNAN);
3015 /*   if (im->gridfit)
3016      apply_gridfit(im); */
3017     /* the actual graph is created by going through the individual
3018        graph elements and then drawing them */
3019     cairo_surface_destroy(im->surface);
3020     switch (im->imgformat) {
3021     case IF_PNG:
3022         im->surface =
3023             cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3024                                        im->ximg * im->zoom,
3025                                        im->yimg * im->zoom);
3026         break;
3027     case IF_PDF:
3028         im->gridfit = 0;
3029         im->surface = strlen(im->graphfile)
3030             ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3031                                        im->yimg * im->zoom)
3032             : cairo_pdf_surface_create_for_stream
3033             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3034         break;
3035     case IF_EPS:
3036         im->gridfit = 0;
3037         im->surface = strlen(im->graphfile)
3038             ?
3039             cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3040                                     im->yimg * im->zoom)
3041             : cairo_ps_surface_create_for_stream
3042             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3043         break;
3044     case IF_SVG:
3045         im->gridfit = 0;
3046         im->surface = strlen(im->graphfile)
3047             ?
3048             cairo_svg_surface_create(im->
3049                                      graphfile,
3050                                      im->ximg * im->zoom, im->yimg * im->zoom)
3051             : cairo_svg_surface_create_for_stream
3052             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3053         cairo_svg_surface_restrict_to_version
3054             (im->surface, CAIRO_SVG_VERSION_1_1);
3055         break;
3056     };
3057     im->cr = cairo_create(im->surface);
3058     cairo_set_antialias(im->cr, im->graph_antialias);
3059     cairo_scale(im->cr, im->zoom, im->zoom);
3060     pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3061     gfx_new_area(im, 0, 0, 0, im->yimg,
3062                  im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3063     gfx_add_point(im, im->ximg, 0);
3064     gfx_close_path(im);
3065     gfx_new_area(im, im->xorigin,
3066                  im->yorigin,
3067                  im->xorigin +
3068                  im->xsize, im->yorigin,
3069                  im->xorigin +
3070                  im->xsize,
3071                  im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3072     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3073     gfx_close_path(im);
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
3362     /* grid_paint also does the text */
3363     if (!(im->extra_flags & ONLY_GRAPH))
3364         grid_paint(im);
3365     if (!(im->extra_flags & ONLY_GRAPH))
3366         axis_paint(im);
3367     /* the RULES are the last thing to paint ... */
3368     for (i = 0; i < im->gdes_c; i++) {
3369
3370         switch (im->gdes[i].gf) {
3371         case GF_HRULE:
3372             if (im->gdes[i].yrule >= im->minval
3373                 && im->gdes[i].yrule <= im->maxval) {
3374                 cairo_save(im->cr);
3375                 if (im->gdes[i].dash) {
3376                     cairo_set_dash(im->cr,
3377                                    im->gdes[i].p_dashes,
3378                                    im->gdes[i].ndash, im->gdes[i].offset);
3379                 }
3380                 gfx_line(im, im->xorigin,
3381                          ytr(im, im->gdes[i].yrule),
3382                          im->xorigin + im->xsize,
3383                          ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3384                 cairo_stroke(im->cr);
3385                 cairo_restore(im->cr);
3386             }
3387             break;
3388         case GF_VRULE:
3389             if (im->gdes[i].xrule >= im->start
3390                 && im->gdes[i].xrule <= im->end) {
3391                 cairo_save(im->cr);
3392                 if (im->gdes[i].dash) {
3393                     cairo_set_dash(im->cr,
3394                                    im->gdes[i].p_dashes,
3395                                    im->gdes[i].ndash, im->gdes[i].offset);
3396                 }
3397                 gfx_line(im,
3398                          xtr(im, im->gdes[i].xrule),
3399                          im->yorigin, xtr(im,
3400                                           im->
3401                                           gdes[i].
3402                                           xrule),
3403                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3404                 cairo_stroke(im->cr);
3405                 cairo_restore(im->cr);
3406             }
3407             break;
3408         default:
3409             break;
3410         }
3411     }
3412
3413
3414     switch (im->imgformat) {
3415     case IF_PNG:
3416     {
3417         cairo_status_t status;
3418
3419         status = strlen(im->graphfile) ?
3420             cairo_surface_write_to_png(im->surface, im->graphfile)
3421             : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3422                                                 im);
3423
3424         if (status != CAIRO_STATUS_SUCCESS) {
3425             rrd_set_error("Could not save png to '%s'", im->graphfile);
3426             return 1;
3427         }
3428         break;
3429     }
3430     default:
3431         if (strlen(im->graphfile)) {
3432             cairo_show_page(im->cr);
3433         } else {
3434             cairo_surface_finish(im->surface);
3435         }
3436         break;
3437     }
3438
3439     return 0;
3440 }
3441
3442
3443 /*****************************************************
3444  * graph stuff 
3445  *****************************************************/
3446
3447 int gdes_alloc(
3448     image_desc_t *im)
3449 {
3450
3451     im->gdes_c++;
3452     if ((im->gdes = (graph_desc_t *)
3453          rrd_realloc(im->gdes, (im->gdes_c)
3454                      * sizeof(graph_desc_t))) == NULL) {
3455         rrd_set_error("realloc graph_descs");
3456         return -1;
3457     }
3458
3459
3460     im->gdes[im->gdes_c - 1].step = im->step;
3461     im->gdes[im->gdes_c - 1].step_orig = im->step;
3462     im->gdes[im->gdes_c - 1].stack = 0;
3463     im->gdes[im->gdes_c - 1].linewidth = 0;
3464     im->gdes[im->gdes_c - 1].debug = 0;
3465     im->gdes[im->gdes_c - 1].start = im->start;
3466     im->gdes[im->gdes_c - 1].start_orig = im->start;
3467     im->gdes[im->gdes_c - 1].end = im->end;
3468     im->gdes[im->gdes_c - 1].end_orig = im->end;
3469     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3470     im->gdes[im->gdes_c - 1].data = NULL;
3471     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3472     im->gdes[im->gdes_c - 1].data_first = 0;
3473     im->gdes[im->gdes_c - 1].p_data = NULL;
3474     im->gdes[im->gdes_c - 1].rpnp = NULL;
3475     im->gdes[im->gdes_c - 1].p_dashes = NULL;
3476     im->gdes[im->gdes_c - 1].shift = 0.0;
3477     im->gdes[im->gdes_c - 1].dash = 0;
3478     im->gdes[im->gdes_c - 1].ndash = 0;
3479     im->gdes[im->gdes_c - 1].offset = 0;
3480     im->gdes[im->gdes_c - 1].col.red = 0.0;
3481     im->gdes[im->gdes_c - 1].col.green = 0.0;
3482     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3483     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3484     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3485     im->gdes[im->gdes_c - 1].format[0] = '\0';
3486     im->gdes[im->gdes_c - 1].strftm = 0;
3487     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3488     im->gdes[im->gdes_c - 1].ds = -1;
3489     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3490     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3491     im->gdes[im->gdes_c - 1].yrule = DNAN;
3492     im->gdes[im->gdes_c - 1].xrule = 0;
3493     return 0;
3494 }
3495
3496 /* copies input untill the first unescaped colon is found
3497    or until input ends. backslashes have to be escaped as well */
3498 int scan_for_col(
3499     const char *const input,
3500     int len,
3501     char *const output)
3502 {
3503     int       inp, outp = 0;
3504
3505     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3506         if (input[inp] == '\\'
3507             && input[inp + 1] != '\0'
3508             && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3509             output[outp++] = input[++inp];
3510         } else {
3511             output[outp++] = input[inp];
3512         }
3513     }
3514     output[outp] = '\0';
3515     return inp;
3516 }
3517
3518 /* Now just a wrapper around rrd_graph_v */
3519 int rrd_graph(
3520     int argc,
3521     char **argv,
3522     char ***prdata,
3523     int *xsize,
3524     int *ysize,
3525     FILE * stream,
3526     double *ymin,
3527     double *ymax)
3528 {
3529     int       prlines = 0;
3530     info_t   *grinfo = NULL;
3531     info_t   *walker;
3532
3533     grinfo = rrd_graph_v(argc, argv);
3534     if (grinfo == NULL)
3535         return -1;
3536     walker = grinfo;
3537     (*prdata) = NULL;
3538     while (walker) {
3539         if (strcmp(walker->key, "image_info") == 0) {
3540             prlines++;
3541             if (((*prdata) =
3542                  rrd_realloc((*prdata),
3543                              (prlines + 1) * sizeof(char *))) == NULL) {
3544                 rrd_set_error("realloc prdata");
3545                 return 0;
3546             }
3547             /* imginfo goes to position 0 in the prdata array */
3548             (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3549                                              + 2) * sizeof(char));
3550             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3551             (*prdata)[prlines] = NULL;
3552         }
3553         /* skip anything else */
3554         walker = walker->next;
3555     }
3556     walker = grinfo;
3557     while (walker) {
3558         if (strcmp(walker->key, "image_width") == 0) {
3559             *xsize = walker->value.u_int;
3560         } else if (strcmp(walker->key, "image_height") == 0) {
3561             *ysize = walker->value.u_int;
3562         } else if (strcmp(walker->key, "value_min") == 0) {
3563             *ymin = walker->value.u_val;
3564         } else if (strcmp(walker->key, "value_max") == 0) {
3565             *ymax = walker->value.u_val;
3566         } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3567             prlines++;
3568             if (((*prdata) =
3569                  rrd_realloc((*prdata),
3570                              (prlines + 1) * sizeof(char *))) == NULL) {
3571                 rrd_set_error("realloc prdata");
3572                 return 0;
3573             }
3574             (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3575                                              + 2) * sizeof(char));
3576             (*prdata)[prlines] = NULL;
3577             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3578         } else if (strcmp(walker->key, "image") == 0) {
3579             fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3580                    (stream ? stream : stdout));
3581         }
3582         /* skip anything else */
3583         walker = walker->next;
3584     }
3585     info_free(grinfo);
3586     return 0;
3587 }
3588
3589
3590 /* Some surgery done on this function, it became ridiculously big.
3591 ** Things moved:
3592 ** - initializing     now in rrd_graph_init()
3593 ** - options parsing  now in rrd_graph_options()
3594 ** - script parsing   now in rrd_graph_script()
3595 */
3596 info_t   *rrd_graph_v(
3597     int argc,
3598     char **argv)
3599 {
3600     image_desc_t im;
3601     info_t   *grinfo;
3602
3603     rrd_graph_init(&im);
3604     /* a dummy surface so that we can measure text sizes for placements */
3605     im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3606     im.cr = cairo_create(im.surface);
3607     rrd_graph_options(argc, argv, &im);
3608     if (rrd_test_error()) {
3609         info_free(im.grinfo);
3610         im_free(&im);
3611         return NULL;
3612     }
3613
3614     if (optind >= argc) {
3615         info_free(im.grinfo);
3616         im_free(&im);
3617         rrd_set_error("missing filename");
3618         return NULL;
3619     }
3620
3621     if (strlen(argv[optind]) >= MAXPATH) {
3622         rrd_set_error("filename (including path) too long");
3623         info_free(im.grinfo);
3624         im_free(&im);
3625         return NULL;
3626     }
3627
3628     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3629     im.graphfile[MAXPATH - 1] = '\0';
3630
3631     if (strcmp(im.graphfile, "-") == 0) {
3632         im.graphfile[0] = '\0';
3633     }
3634
3635     rrd_graph_script(argc, argv, &im, 1);
3636     if (rrd_test_error()) {
3637         info_free(im.grinfo);
3638         im_free(&im);
3639         return NULL;
3640     }
3641
3642     /* Everything is now read and the actual work can start */
3643
3644     if (graph_paint(&im) == -1) {
3645         info_free(im.grinfo);
3646         im_free(&im);
3647         return NULL;
3648     }
3649
3650
3651     /* The image is generated and needs to be output.
3652      ** Also, if needed, print a line with information about the image.
3653      */
3654
3655     if (im.imginfo) {
3656         infoval   info;
3657
3658         info.u_str =
3659             sprintf_alloc(im.imginfo,
3660                           im.graphfile,
3661                           (long) (im.zoom *
3662                                   im.ximg), (long) (im.zoom * im.yimg));
3663         grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3664         free(info.u_str);
3665     }
3666     if (im.rendered_image) {
3667         infoval   img;
3668
3669         img.u_blo.size = im.rendered_image_size;
3670         img.u_blo.ptr = im.rendered_image;
3671         grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3672     }
3673     grinfo = im.grinfo;
3674     im_free(&im);
3675     return grinfo;
3676 }
3677
3678 void rrd_graph_init(
3679     image_desc_t
3680     *im)
3681 {
3682     unsigned int i;
3683
3684 #ifdef HAVE_TZSET
3685     tzset();
3686 #endif
3687 #ifdef HAVE_SETLOCALE
3688     setlocale(LC_TIME, "");
3689 #ifdef HAVE_MBSTOWCS
3690     setlocale(LC_CTYPE, "");
3691 #endif
3692 #endif
3693     im->base = 1000;
3694     im->cr = NULL;
3695     im->draw_x_grid = 1;
3696     im->draw_y_grid = 1;
3697     im->extra_flags = 0;
3698     im->font_options = cairo_font_options_create();
3699     im->forceleftspace = 0;
3700     im->gdes_c = 0;
3701     im->gdes = NULL;
3702     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3703     im->grid_dash_off = 1;
3704     im->grid_dash_on = 1;
3705     im->gridfit = 1;
3706     im->grinfo = (info_t *) NULL;
3707     im->grinfo_current = (info_t *) NULL;
3708     im->imgformat = IF_PNG;
3709     im->imginfo = NULL;
3710     im->lazy = 0;
3711     im->logarithmic = 0;
3712     im->maxval = DNAN;
3713     im->minval = 0;
3714     im->minval = DNAN;
3715     im->prt_c = 0;
3716     im->rigid = 0;
3717     im->rendered_image_size = 0;
3718     im->rendered_image = NULL;
3719     im->slopemode = 0;
3720     im->step = 0;
3721     im->surface = NULL;
3722     im->symbol = ' ';
3723     im->tabwidth = 40.0;
3724     im->title[0] = '\0';
3725     im->unitsexponent = 9999;
3726     im->unitslength = 6;
3727     im->viewfactor = 1.0;
3728     im->watermark[0] = '\0';
3729     im->ximg = 0;
3730     im->xlab_user.minsec = -1;
3731     im->xorigin = 0;
3732     im->xsize = 400;
3733     im->ygridstep = DNAN;
3734     im->yimg = 0;
3735     im->ylegend[0] = '\0';
3736     im->yorigin = 0;
3737     im->ysize = 100;
3738     im->zoom = 1;
3739     cairo_font_options_set_hint_style
3740         (im->font_options, CAIRO_HINT_STYLE_FULL);
3741     cairo_font_options_set_hint_metrics
3742         (im->font_options, CAIRO_HINT_METRICS_ON);
3743     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3744     for (i = 0; i < DIM(graph_col); i++)
3745         im->graph_col[i] = graph_col[i];
3746 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3747     {
3748         char     *windir;
3749         char      rrd_win_default_font[1000];
3750
3751         windir = getenv("windir");
3752         /* %windir% is something like D:\windows or C:\winnt */
3753         if (windir != NULL) {
3754             strncpy(rrd_win_default_font, windir, 500);
3755             rrd_win_default_font[500] = '\0';
3756             strcat(rrd_win_default_font, "\\fonts\\");
3757             strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3758             for (i = 0; i < DIM(text_prop); i++) {
3759                 strncpy(text_prop[i].font,
3760                         rrd_win_default_font, sizeof(text_prop[i].font) - 1);
3761                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3762             }
3763         }
3764     }
3765 #endif
3766     {
3767         char     *deffont;
3768
3769         deffont = getenv("RRD_DEFAULT_FONT");
3770         if (deffont != NULL) {
3771             for (i = 0; i < DIM(text_prop); i++) {
3772                 strncpy(text_prop[i].font, deffont,
3773                         sizeof(text_prop[i].font) - 1);
3774                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3775             }
3776         }
3777     }
3778     for (i = 0; i < DIM(text_prop); i++) {
3779         im->text_prop[i].size = text_prop[i].size;
3780         strcpy(im->text_prop[i].font, text_prop[i].font);
3781     }
3782 }
3783
3784 void rrd_graph_options(
3785     int argc,
3786     char *argv[],
3787     image_desc_t
3788     *im)
3789 {
3790     int       stroff;
3791     char     *parsetime_error = NULL;
3792     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3793     time_t    start_tmp = 0, end_tmp = 0;
3794     long      long_tmp;
3795     struct rrd_time_value start_tv, end_tv;
3796     long unsigned int color;
3797     char     *old_locale = "";
3798
3799     /* defines for long options without a short equivalent. should be bytes,
3800        and may not collide with (the ASCII value of) short options */
3801 #define LONGOPT_UNITS_SI 255
3802     struct option long_options[] = {
3803         {
3804          "start", required_argument, 0, 's'}, {
3805                                                "end", required_argument, 0,
3806                                                'e'}, {
3807                                                       "x-grid",
3808                                                       required_argument, 0,
3809                                                       'x'}, {
3810                                                              "y-grid",
3811                                                              required_argument,
3812                                                              0, 'y'}, {
3813                                                                        "vertical-label",
3814                                                                        required_argument,
3815                                                                        0,
3816                                                                        'v'}, {
3817                                                                               "width",
3818                                                                               required_argument,
3819                                                                               0,
3820                                                                               'w'},
3821         {
3822          "height", required_argument, 0, 'h'}, {
3823                                                 "full-size-mode", no_argument,
3824                                                 0, 'D'}, {
3825                                                           "interlaced",
3826                                                           no_argument, 0,
3827                                                           'i'}, {
3828                                                                  "upper-limit",
3829                                                                  required_argument,
3830                                                                  0,
3831                                                                  'u'}, {
3832                                                                         "lower-limit",
3833                                                                         required_argument,
3834                                                                         0,
3835                                                                         'l'}, {
3836                                                                                "rigid",
3837                                                                                no_argument,
3838                                                                                0,
3839                                                                                'r'},
3840         {
3841          "base", required_argument, 0, 'b'}, {
3842                                               "logarithmic", no_argument, 0,
3843                                               'o'}, {
3844                                                      "color",
3845                                                      required_argument, 0,
3846                                                      'c'}, {
3847                                                             "font",
3848                                                             required_argument,
3849                                                             0, 'n'}, {
3850                                                                       "title",
3851                                                                       required_argument,
3852                                                                       0, 't'},
3853         {
3854          "imginfo", required_argument, 0, 'f'}, {
3855                                                  "imgformat",
3856                                                  required_argument, 0, 'a'}, {
3857                                                                               "lazy",
3858                                                                               no_argument,
3859                                                                               0,
3860                                                                               'z'},
3861         {
3862          "zoom", required_argument, 0, 'm'}, {
3863                                               "no-legend", no_argument, 0,
3864                                               'g'}, {
3865                                                      "force-rules-legend",
3866                                                      no_argument,
3867                                                      0, 'F'}, {
3868                                                                "only-graph",
3869                                                                no_argument, 0,
3870                                                                'j'}, {
3871                                                                       "alt-y-grid",
3872                                                                       no_argument,
3873                                                                       0, 'Y'},
3874         {
3875          "no-minor", no_argument, 0, 'I'}, {
3876                                             "slope-mode", no_argument, 0,
3877                                             'E'}, {
3878                                                    "alt-autoscale",
3879                                                    no_argument, 0, 'A'}, {
3880                                                                           "alt-autoscale-min",
3881                                                                           no_argument,
3882                                                                           0,
3883                                                                           'J'}, {
3884                                                                                  "alt-autoscale-max",
3885                                                                                  no_argument,
3886                                                                                  0,
3887                                                                                  'M'}, {
3888                                                                                         "no-gridfit",
3889                                                                                         no_argument,
3890                                                                                         0,
3891                                                                                         'N'},
3892         {
3893          "units-exponent", required_argument,
3894          0, 'X'}, {
3895                    "units-length", required_argument,
3896                    0, 'L'}, {
3897                              "units", required_argument, 0,
3898                              LONGOPT_UNITS_SI}, {
3899                                                  "step", required_argument, 0,
3900                                                  'S'}, {
3901                                                         "tabwidth",
3902                                                         required_argument, 0,
3903                                                         'T'}, {
3904                                                                "font-render-mode",
3905                                                                required_argument,
3906                                                                0, 'R'}, {
3907                                                                          "graph-render-mode",
3908                                                                          required_argument,
3909                                                                          0,
3910                                                                          'G'},
3911         {
3912          "font-smoothing-threshold",
3913          required_argument, 0, 'B'}, {
3914                                       "watermark", required_argument, 0, 'W'}, {
3915                                                                                 "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 */
3916         {
3917          0, 0, 0, 0}
3918     };
3919     optind = 0;
3920     opterr = 0;         /* initialize getopt */
3921     parsetime("end-24h", &start_tv);
3922     parsetime("now", &end_tv);
3923     while (1) {
3924         int       option_index = 0;
3925         int       opt;
3926         int       col_start, col_end;
3927
3928         opt = getopt_long(argc, argv,
3929                           "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",
3930                           long_options, &option_index);
3931         if (opt == EOF)
3932             break;
3933         switch (opt) {
3934         case 'I':
3935             im->extra_flags |= NOMINOR;
3936             break;
3937         case 'Y':
3938             im->extra_flags |= ALTYGRID;
3939             break;
3940         case 'A':
3941             im->extra_flags |= ALTAUTOSCALE;
3942             break;
3943         case 'J':
3944             im->extra_flags |= ALTAUTOSCALE_MIN;
3945             break;
3946         case 'M':
3947             im->extra_flags |= ALTAUTOSCALE_MAX;
3948             break;
3949         case 'j':
3950             im->extra_flags |= ONLY_GRAPH;
3951             break;
3952         case 'g':
3953             im->extra_flags |= NOLEGEND;
3954             break;
3955         case 'F':
3956             im->extra_flags |= FORCE_RULES_LEGEND;
3957             break;
3958         case LONGOPT_UNITS_SI:
3959             if (im->extra_flags & FORCE_UNITS) {
3960                 rrd_set_error("--units can only be used once!");
3961                 setlocale(LC_NUMERIC, old_locale);
3962                 return;
3963             }
3964             if (strcmp(optarg, "si") == 0)
3965                 im->extra_flags |= FORCE_UNITS_SI;
3966             else {
3967                 rrd_set_error("invalid argument for --units: %s", optarg);
3968                 return;
3969             }
3970             break;
3971         case 'X':
3972             im->unitsexponent = atoi(optarg);
3973             break;
3974         case 'L':
3975             im->unitslength = atoi(optarg);
3976             im->forceleftspace = 1;
3977             break;
3978         case 'T':
3979             old_locale = setlocale(LC_NUMERIC, "C");
3980             im->tabwidth = atof(optarg);
3981             setlocale(LC_NUMERIC, old_locale);
3982             break;
3983         case 'S':
3984             old_locale = setlocale(LC_NUMERIC, "C");
3985             im->step = atoi(optarg);
3986             setlocale(LC_NUMERIC, old_locale);
3987             break;
3988         case 'N':
3989             im->gridfit = 0;
3990             break;
3991         case 's':
3992             if ((parsetime_error = parsetime(optarg, &start_tv))) {
3993                 rrd_set_error("start time: %s", parsetime_error);
3994                 return;
3995             }
3996             break;
3997         case 'e':
3998             if ((parsetime_error = parsetime(optarg, &end_tv))) {
3999                 rrd_set_error("end time: %s", parsetime_error);
4000                 return;
4001             }
4002             break;
4003         case 'x':
4004             if (strcmp(optarg, "none") == 0) {
4005                 im->draw_x_grid = 0;
4006                 break;
4007             };
4008             if (sscanf(optarg,
4009                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4010                        scan_gtm,
4011                        &im->xlab_user.gridst,
4012                        scan_mtm,
4013                        &im->xlab_user.mgridst,
4014                        scan_ltm,
4015                        &im->xlab_user.labst,
4016                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4017                 strncpy(im->xlab_form, optarg + stroff,
4018                         sizeof(im->xlab_form) - 1);
4019                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4020                 if ((int)
4021                     (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4022                     rrd_set_error("unknown keyword %s", scan_gtm);
4023                     return;
4024                 } else if ((int)
4025                            (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4026                            == -1) {
4027                     rrd_set_error("unknown keyword %s", scan_mtm);
4028                     return;
4029                 } else if ((int)
4030                            (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4031                     rrd_set_error("unknown keyword %s", scan_ltm);
4032                     return;
4033                 }
4034                 im->xlab_user.minsec = 1;
4035                 im->xlab_user.stst = im->xlab_form;
4036             } else {
4037                 rrd_set_error("invalid x-grid format");
4038                 return;
4039             }
4040             break;
4041         case 'y':
4042
4043             if (strcmp(optarg, "none") == 0) {
4044                 im->draw_y_grid = 0;
4045                 break;
4046             };
4047             old_locale = setlocale(LC_NUMERIC, "C");
4048             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4049                 setlocale(LC_NUMERIC, old_locale);
4050                 if (im->ygridstep <= 0) {
4051                     rrd_set_error("grid step must be > 0");
4052                     return;
4053                 } else if (im->ylabfact < 1) {
4054                     rrd_set_error("label factor must be > 0");
4055                     return;
4056                 }
4057             } else {
4058                 setlocale(LC_NUMERIC, old_locale);
4059                 rrd_set_error("invalid y-grid format");
4060                 return;
4061             }
4062             break;
4063         case 'v':
4064             strncpy(im->ylegend, optarg, 150);
4065             im->ylegend[150] = '\0';
4066             break;
4067         case 'u':
4068             old_locale = setlocale(LC_NUMERIC, "C");
4069             im->maxval = atof(optarg);
4070             setlocale(LC_NUMERIC, old_locale);
4071             break;
4072         case 'l':
4073             old_locale = setlocale(LC_NUMERIC, "C");
4074             im->minval = atof(optarg);
4075             setlocale(LC_NUMERIC, old_locale);
4076             break;
4077         case 'b':
4078             im->base = atol(optarg);
4079             if (im->base != 1024 && im->base != 1000) {
4080                 rrd_set_error
4081                     ("the only sensible value for base apart from 1000 is 1024");
4082                 return;
4083             }
4084             break;
4085         case 'w':
4086             long_tmp = atol(optarg);
4087             if (long_tmp < 10) {
4088                 rrd_set_error("width below 10 pixels");
4089                 return;
4090             }
4091             im->xsize = long_tmp;
4092             break;
4093         case 'h':
4094             long_tmp = atol(optarg);
4095             if (long_tmp < 10) {
4096                 rrd_set_error("height below 10 pixels");
4097                 return;
4098             }
4099             im->ysize = long_tmp;
4100             break;
4101         case 'D':
4102             im->extra_flags |= FULL_SIZE_MODE;
4103             break;
4104         case 'i':
4105             /* interlaced png not supported at the moment */
4106             break;
4107         case 'r':
4108             im->rigid = 1;
4109             break;
4110         case 'f':
4111             im->imginfo = optarg;
4112             break;
4113         case 'a':
4114             if ((int)
4115                 (im->imgformat = if_conv(optarg)) == -1) {
4116                 rrd_set_error("unsupported graphics format '%s'", optarg);
4117                 return;
4118             }
4119             break;
4120         case 'z':
4121             im->lazy = 1;
4122             break;
4123         case 'E':
4124             im->slopemode = 1;
4125             break;
4126         case 'o':
4127             im->logarithmic = 1;
4128             break;
4129         case 'c':
4130             if (sscanf(optarg,
4131                        "%10[A-Z]#%n%8lx%n",
4132                        col_nam, &col_start, &color, &col_end) == 2) {
4133                 int       ci;
4134                 int       col_len = col_end - col_start;
4135
4136                 switch (col_len) {
4137                 case 3:
4138                     color =
4139                         (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4140                                                          0x011000) |
4141                          ((color & 0x00F)
4142                           * 0x001100)
4143                          | 0x000000FF);
4144                     break;
4145                 case 4:
4146                     color =
4147                         (((color & 0xF000) *
4148                           0x11000) | ((color & 0x0F00) *
4149                                       0x01100) | ((color &
4150                                                    0x00F0) *
4151                                                   0x00110) |
4152                          ((color & 0x000F) * 0x00011)
4153                         );
4154                     break;
4155                 case 6:
4156                     color = (color << 8) + 0xff /* shift left by 8 */ ;
4157                     break;
4158                 case 8:
4159                     break;
4160                 default:
4161                     rrd_set_error("the color format is #RRGGBB[AA]");
4162                     return;
4163                 }
4164                 if ((ci = grc_conv(col_nam)) != -1) {
4165                     im->graph_col[ci] = gfx_hex_to_col(color);
4166                 } else {
4167                     rrd_set_error("invalid color name '%s'", col_nam);
4168                     return;
4169                 }
4170             } else {
4171                 rrd_set_error("invalid color def format");
4172                 return;
4173             }
4174             break;
4175         case 'n':{
4176             char      prop[15];
4177             double    size = 1;
4178             int       end;
4179
4180             old_locale = setlocale(LC_NUMERIC, "C");
4181             if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4182                 int       sindex, propidx;
4183
4184                 setlocale(LC_NUMERIC, old_locale);
4185                 if ((sindex = text_prop_conv(prop)) != -1) {
4186                     for (propidx = sindex;
4187                          propidx < TEXT_PROP_LAST; propidx++) {
4188                         if (size > 0) {
4189                             im->text_prop[propidx].size = size;
4190                         }
4191                         if ((int) strlen(prop) > end) {
4192                             if (prop[end] == ':') {
4193                                 strncpy(im->text_prop[propidx].font,
4194                                         prop + end + 1, 255);
4195                                 im->text_prop[propidx].font[255] = '\0';
4196                             } else {
4197                                 rrd_set_error
4198                                     ("expected after font size in '%s'",
4199                                      prop);
4200                                 return;
4201                             }
4202                         }
4203                         if (propidx == sindex && sindex != 0)
4204                             break;
4205                     }
4206                 } else {
4207                     rrd_set_error("invalid fonttag '%s'", prop);
4208                     return;
4209                 }
4210             } else {
4211                 setlocale(LC_NUMERIC, old_locale);
4212                 rrd_set_error("invalid text property format");
4213                 return;
4214             }
4215             break;
4216         }
4217         case 'm':
4218             old_locale = setlocale(LC_NUMERIC, "C");
4219             im->zoom = atof(optarg);
4220             setlocale(LC_NUMERIC, old_locale);
4221             if (im->zoom <= 0.0) {
4222                 rrd_set_error("zoom factor must be > 0");
4223                 return;
4224             }
4225             break;
4226         case 't':
4227             strncpy(im->title, optarg, 150);
4228             im->title[150] = '\0';
4229             break;
4230         case 'R':
4231             if (strcmp(optarg, "normal") == 0) {
4232                 cairo_font_options_set_antialias
4233                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4234                 cairo_font_options_set_hint_style
4235                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4236             } else if (strcmp(optarg, "light") == 0) {
4237                 cairo_font_options_set_antialias
4238                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4239                 cairo_font_options_set_hint_style
4240                     (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4241             } else if (strcmp(optarg, "mono") == 0) {
4242                 cairo_font_options_set_antialias
4243                     (im->font_options, CAIRO_ANTIALIAS_NONE);
4244                 cairo_font_options_set_hint_style
4245                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4246             } else {
4247                 rrd_set_error("unknown font-render-mode '%s'", optarg);
4248                 return;
4249             }
4250             break;
4251         case 'G':
4252             if (strcmp(optarg, "normal") == 0)
4253                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4254             else if (strcmp(optarg, "mono") == 0)
4255                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4256             else {
4257                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4258                 return;
4259             }
4260             break;
4261         case 'B':
4262             /* not supported curently */
4263             break;
4264         case 'W':
4265             strncpy(im->watermark, optarg, 100);
4266             im->watermark[99] = '\0';
4267             break;
4268         case '?':
4269             if (optopt != 0)
4270                 rrd_set_error("unknown option '%c'", optopt);
4271             else
4272                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4273             return;
4274         }
4275     }
4276
4277     if (im->logarithmic && im->minval <= 0) {
4278         rrd_set_error
4279             ("for a logarithmic yaxis you must specify a lower-limit > 0");
4280         return;
4281     }
4282
4283     if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4284         /* error string is set in parsetime.c */
4285         return;
4286     }
4287
4288     if (start_tmp < 3600 * 24 * 365 * 10) {
4289         rrd_set_error
4290             ("the first entry to fetch should be after 1980 (%ld)",
4291              start_tmp);
4292         return;
4293     }
4294
4295     if (end_tmp < start_tmp) {
4296         rrd_set_error
4297             ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4298         return;
4299     }
4300
4301     im->start = start_tmp;
4302     im->end = end_tmp;
4303     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4304 }
4305
4306 int rrd_graph_color(
4307     image_desc_t
4308     *im,
4309     char *var,
4310     char *err,
4311     int optional)
4312 {
4313     char     *color;
4314     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4315
4316     color = strstr(var, "#");
4317     if (color == NULL) {
4318         if (optional == 0) {
4319             rrd_set_error("Found no color in %s", err);
4320             return 0;
4321         }
4322         return 0;
4323     } else {
4324         int       n = 0;
4325         char     *rest;
4326         long unsigned int col;
4327
4328         rest = strstr(color, ":");
4329         if (rest != NULL)
4330             n = rest - color;
4331         else
4332             n = strlen(color);
4333         switch (n) {
4334         case 7:
4335             sscanf(color, "#%6lx%n", &col, &n);
4336             col = (col << 8) + 0xff /* shift left by 8 */ ;
4337             if (n != 7)
4338                 rrd_set_error("Color problem in %s", err);
4339             break;
4340         case 9:
4341             sscanf(color, "#%8lx%n", &col, &n);
4342             if (n == 9)
4343                 break;
4344         default:
4345             rrd_set_error("Color problem in %s", err);
4346         }
4347         if (rrd_test_error())
4348             return 0;
4349         gdp->col = gfx_hex_to_col(col);
4350         return n;
4351     }
4352 }
4353
4354
4355 int bad_format(
4356     char *fmt)
4357 {
4358     char     *ptr;
4359     int       n = 0;
4360
4361     ptr = fmt;
4362     while (*ptr != '\0')
4363         if (*ptr++ == '%') {
4364
4365             /* line cannot end with percent char */
4366             if (*ptr == '\0')
4367                 return 1;
4368             /* '%s', '%S' and '%%' are allowed */
4369             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4370                 ptr++;
4371             /* %c is allowed (but use only with vdef!) */
4372             else if (*ptr == 'c') {
4373                 ptr++;
4374                 n = 1;
4375             }
4376
4377             /* or else '% 6.2lf' and such are allowed */
4378             else {
4379                 /* optional padding character */
4380                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4381                     ptr++;
4382                 /* This should take care of 'm.n' with all three optional */
4383                 while (*ptr >= '0' && *ptr <= '9')
4384                     ptr++;
4385                 if (*ptr == '.')
4386                     ptr++;
4387                 while (*ptr >= '0' && *ptr <= '9')
4388                     ptr++;
4389                 /* Either 'le', 'lf' or 'lg' must follow here */
4390                 if (*ptr++ != 'l')
4391                     return 1;
4392                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4393                     ptr++;
4394                 else
4395                     return 1;
4396                 n++;
4397             }
4398         }
4399
4400     return (n != 1);
4401 }
4402
4403
4404 int vdef_parse(
4405     struct graph_desc_t
4406     *gdes,
4407     const char *const str)
4408 {
4409     /* A VDEF currently is either "func" or "param,func"
4410      * so the parsing is rather simple.  Change if needed.
4411      */
4412     double    param;
4413     char      func[30];
4414     int       n;
4415     char     *old_locale;
4416
4417     n = 0;
4418     old_locale = setlocale(LC_NUMERIC, "C");
4419     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4420     setlocale(LC_NUMERIC, old_locale);
4421     if (n == (int) strlen(str)) {   /* matched */
4422         ;
4423     } else {
4424         n = 0;
4425         sscanf(str, "%29[A-Z]%n", func, &n);
4426         if (n == (int) strlen(str)) {   /* matched */
4427             param = DNAN;
4428         } else {
4429             rrd_set_error
4430                 ("Unknown function string '%s' in VDEF '%s'",
4431                  str, gdes->vname);
4432             return -1;
4433         }
4434     }
4435     if (!strcmp("PERCENT", func))
4436         gdes->vf.op = VDEF_PERCENT;
4437     else if (!strcmp("MAXIMUM", func))
4438         gdes->vf.op = VDEF_MAXIMUM;
4439     else if (!strcmp("AVERAGE", func))
4440         gdes->vf.op = VDEF_AVERAGE;
4441     else if (!strcmp("STDEV", func))
4442         gdes->vf.op = VDEF_STDEV;
4443     else if (!strcmp("MINIMUM", func))
4444         gdes->vf.op = VDEF_MINIMUM;
4445     else if (!strcmp("TOTAL", func))
4446         gdes->vf.op = VDEF_TOTAL;
4447     else if (!strcmp("FIRST", func))
4448         gdes->vf.op = VDEF_FIRST;
4449     else if (!strcmp("LAST", func))
4450         gdes->vf.op = VDEF_LAST;
4451     else if (!strcmp("LSLSLOPE", func))
4452         gdes->vf.op = VDEF_LSLSLOPE;
4453     else if (!strcmp("LSLINT", func))
4454         gdes->vf.op = VDEF_LSLINT;
4455     else if (!strcmp("LSLCORREL", func))
4456         gdes->vf.op = VDEF_LSLCORREL;
4457     else {
4458         rrd_set_error
4459             ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4460         return -1;
4461     };
4462     switch (gdes->vf.op) {
4463     case VDEF_PERCENT:
4464         if (isnan(param)) { /* no parameter given */
4465             rrd_set_error
4466                 ("Function '%s' needs parameter in VDEF '%s'\n",
4467                  func, gdes->vname);
4468             return -1;
4469         };
4470         if (param >= 0.0 && param <= 100.0) {
4471             gdes->vf.param = param;
4472             gdes->vf.val = DNAN;    /* undefined */
4473             gdes->vf.when = 0;  /* undefined */
4474         } else {
4475             rrd_set_error
4476                 ("Parameter '%f' out of range in VDEF '%s'\n",
4477                  param, gdes->vname);
4478             return -1;
4479         };
4480         break;
4481     case VDEF_MAXIMUM:
4482     case VDEF_AVERAGE:
4483     case VDEF_STDEV:
4484     case VDEF_MINIMUM:
4485     case VDEF_TOTAL:
4486     case VDEF_FIRST:
4487     case VDEF_LAST:
4488     case VDEF_LSLSLOPE:
4489     case VDEF_LSLINT:
4490     case VDEF_LSLCORREL:
4491         if (isnan(param)) {
4492             gdes->vf.param = DNAN;
4493             gdes->vf.val = DNAN;
4494             gdes->vf.when = 0;
4495         } else {
4496             rrd_set_error
4497                 ("Function '%s' needs no parameter in VDEF '%s'\n",
4498                  func, gdes->vname);
4499             return -1;
4500         };
4501         break;
4502     };
4503     return 0;
4504 }
4505
4506
4507 int vdef_calc(
4508     image_desc_t *im,
4509     int gdi)
4510 {
4511     graph_desc_t *src, *dst;
4512     rrd_value_t *data;
4513     long      step, steps;
4514
4515     dst = &im->gdes[gdi];
4516     src = &im->gdes[dst->vidx];
4517     data = src->data + src->ds;
4518     steps = (src->end - src->start) / src->step;
4519 #if 0
4520     printf
4521         ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4522          src->start, src->end, steps);
4523 #endif
4524     switch (dst->vf.op) {
4525     case VDEF_PERCENT:{
4526         rrd_value_t *array;
4527         int       field;
4528         if ((array = malloc(steps * sizeof(double))) == NULL) {
4529             rrd_set_error("malloc VDEV_PERCENT");
4530             return -1;
4531         }
4532         for (step = 0; step < steps; step++) {
4533             array[step] = data[step * src->ds_cnt];
4534         }
4535         qsort(array, step, sizeof(double), vdef_percent_compar);
4536         field = (steps - 1) * dst->vf.param / 100;
4537         dst->vf.val = array[field];
4538         dst->vf.when = 0;   /* no time component */
4539         free(array);
4540 #if 0
4541         for (step = 0; step < steps; step++)
4542             printf("DEBUG: %3li:%10.2f %c\n",
4543                    step, array[step], step == field ? '*' : ' ');
4544 #endif
4545     }
4546         break;
4547     case VDEF_MAXIMUM:
4548         step = 0;
4549         while (step != steps && isnan(data[step * src->ds_cnt]))
4550             step++;
4551         if (step == steps) {
4552             dst->vf.val = DNAN;
4553             dst->vf.when = 0;
4554         } else {
4555             dst->vf.val = data[step * src->ds_cnt];
4556             dst->vf.when = src->start + (step + 1) * src->step;
4557         }
4558         while (step != steps) {
4559             if (finite(data[step * src->ds_cnt])) {
4560                 if (data[step * src->ds_cnt] > dst->vf.val) {
4561                     dst->vf.val = data[step * src->ds_cnt];
4562                     dst->vf.when = src->start + (step + 1) * src->step;
4563                 }
4564             }
4565             step++;
4566         }
4567         break;
4568     case VDEF_TOTAL:
4569     case VDEF_STDEV:
4570     case VDEF_AVERAGE:{
4571         int       cnt = 0;
4572         double    sum = 0.0;
4573         double    average = 0.0;
4574
4575         for (step = 0; step < steps; step++) {
4576             if (finite(data[step * src->ds_cnt])) {
4577                 sum += data[step * src->ds_cnt];
4578                 cnt++;
4579             };
4580         }
4581         if (cnt) {
4582             if (dst->vf.op == VDEF_TOTAL) {
4583                 dst->vf.val = sum * src->step;
4584                 dst->vf.when = 0;   /* no time component */
4585             } else if (dst->vf.op == VDEF_AVERAGE) {
4586                 dst->vf.val = sum / cnt;
4587                 dst->vf.when = 0;   /* no time component */
4588             } else {
4589                 average = sum / cnt;
4590                 sum = 0.0;
4591                 for (step = 0; step < steps; step++) {
4592                     if (finite(data[step * src->ds_cnt])) {
4593                         sum += pow((data[step * src->ds_cnt] - average), 2.0);
4594                     };
4595                 }
4596                 dst->vf.val = pow(sum / cnt, 0.5);
4597                 dst->vf.when = 0;   /* no time component */
4598             };
4599         } else {
4600             dst->vf.val = DNAN;
4601             dst->vf.when = 0;
4602         }
4603     }
4604         break;
4605     case VDEF_MINIMUM:
4606         step = 0;
4607         while (step != steps && isnan(data[step * src->ds_cnt]))
4608             step++;
4609         if (step == steps) {
4610             dst->vf.val = DNAN;
4611             dst->vf.when = 0;
4612         } else {
4613             dst->vf.val = data[step * src->ds_cnt];
4614             dst->vf.when = src->start + (step + 1) * src->step;
4615         }
4616         while (step != steps) {
4617             if (finite(data[step * src->ds_cnt])) {
4618                 if (data[step * src->ds_cnt] < dst->vf.val) {
4619                     dst->vf.val = data[step * src->ds_cnt];
4620                     dst->vf.when = src->start + (step + 1) * src->step;
4621                 }
4622             }
4623             step++;
4624         }
4625         break;
4626     case VDEF_FIRST:
4627         /* The time value returned here is one step before the
4628          * actual time value.  This is the start of the first
4629          * non-NaN interval.
4630          */
4631         step = 0;
4632         while (step != steps && isnan(data[step * src->ds_cnt]))
4633             step++;
4634         if (step == steps) {    /* all entries were NaN */
4635             dst->vf.val = DNAN;
4636             dst->vf.when = 0;
4637         } else {
4638             dst->vf.val = data[step * src->ds_cnt];
4639             dst->vf.when = src->start + step * src->step;
4640         }
4641         break;
4642     case VDEF_LAST:
4643         /* The time value returned here is the
4644          * actual time value.  This is the end of the last
4645          * non-NaN interval.
4646          */
4647         step = steps - 1;
4648         while (step >= 0 && isnan(data[step * src->ds_cnt]))
4649             step--;
4650         if (step < 0) { /* all entries were NaN */
4651             dst->vf.val = DNAN;
4652             dst->vf.when = 0;
4653         } else {
4654             dst->vf.val = data[step * src->ds_cnt];
4655             dst->vf.when = src->start + (step + 1) * src->step;
4656         }
4657         break;
4658     case VDEF_LSLSLOPE:
4659     case VDEF_LSLINT:
4660     case VDEF_LSLCORREL:{
4661         /* Bestfit line by linear least squares method */
4662
4663         int       cnt = 0;
4664         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4665
4666         SUMx = 0;
4667         SUMy = 0;
4668         SUMxy = 0;
4669         SUMxx = 0;
4670         SUMyy = 0;
4671         for (step = 0; step < steps; step++) {
4672             if (finite(data[step * src->ds_cnt])) {
4673                 cnt++;
4674                 SUMx += step;
4675                 SUMxx += step * step;
4676                 SUMxy += step * data[step * src->ds_cnt];
4677                 SUMy += data[step * src->ds_cnt];
4678                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4679             };
4680         }
4681
4682         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4683         y_intercept = (SUMy - slope * SUMx) / cnt;
4684         correl =
4685             (SUMxy -
4686              (SUMx * SUMy) / cnt) /
4687             sqrt((SUMxx -
4688                   (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4689         if (cnt) {
4690             if (dst->vf.op == VDEF_LSLSLOPE) {
4691                 dst->vf.val = slope;
4692                 dst->vf.when = 0;
4693             } else if (dst->vf.op == VDEF_LSLINT) {
4694                 dst->vf.val = y_intercept;
4695                 dst->vf.when = 0;
4696             } else if (dst->vf.op == VDEF_LSLCORREL) {
4697                 dst->vf.val = correl;
4698                 dst->vf.when = 0;
4699             };
4700         } else {
4701             dst->vf.val = DNAN;
4702             dst->vf.when = 0;
4703         }
4704     }
4705         break;
4706     }
4707     return 0;
4708 }
4709
4710 /* NaN < -INF < finite_values < INF */
4711 int vdef_percent_compar(
4712     const void
4713     *a,
4714     const void
4715     *b)
4716 {
4717     /* Equality is not returned; this doesn't hurt except
4718      * (maybe) for a little performance.
4719      */
4720
4721     /* First catch NaN values. They are smallest */
4722     if (isnan(*(double *) a))
4723         return -1;
4724     if (isnan(*(double *) b))
4725         return 1;
4726     /* NaN doesn't reach this part so INF and -INF are extremes.
4727      * The sign from isinf() is compatible with the sign we return
4728      */
4729     if (isinf(*(double *) a))
4730         return isinf(*(double *) a);
4731     if (isinf(*(double *) b))
4732         return isinf(*(double *) b);
4733     /* If we reach this, both values must be finite */
4734     if (*(double *) a < *(double *) b)
4735         return -1;
4736     else
4737         return 1;
4738 }
4739
4740 void grinfo_push(
4741     image_desc_t *im,
4742     char *key,
4743     enum info_type type,
4744     infoval value)
4745 {
4746     im->grinfo_current = info_push(im->grinfo_current, key, type, value);
4747     if (im->grinfo == NULL) {
4748         im->grinfo = im->grinfo_current;
4749     }
4750 }