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