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