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