removed c++ comments //
[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
2717     if (im->extra_flags & ONLY_GRAPH) {
2718         im->xorigin = 0;
2719         im->ximg = im->xsize;
2720         im->yimg = im->ysize;
2721         im->yorigin = im->ysize;
2722         ytr(im, DNAN);
2723         return 0;
2724     }
2725
2726     /** +---+--------------------------------------------+
2727      ** | y |...............graph title..................|
2728      ** |   +---+-------------------------------+--------+
2729      ** | a | y |                               |        |
2730      ** | x |   |                               |        |
2731      ** | i | a |                               |  pie   |
2732      ** | s | x |       main graph area         | chart  |
2733      ** |   | i |                               |  area  |
2734      ** | t | s |                               |        |
2735      ** | i |   |                               |        |
2736      ** | t | l |                               |        |
2737      ** | l | b +-------------------------------+--------+
2738      ** | e | l |       x axis labels           |        |
2739      ** +---+---+-------------------------------+--------+
2740      ** |....................legends.....................|
2741      ** +------------------------------------------------+
2742      ** |                   watermark                    |
2743      ** +------------------------------------------------+
2744      */
2745
2746     if (im->ylegend[0] != '\0') {
2747         Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2748     }
2749
2750     if (im->title[0] != '\0') {
2751         /* The title is placed "inbetween" two text lines so it
2752          ** automatically has some vertical spacing.  The horizontal
2753          ** spacing is added here, on each side.
2754          */
2755         /* if necessary, reduce the font size of the title until it fits the image width */
2756         Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2757     }
2758
2759     if (elements) {
2760         if (im->draw_x_grid) {
2761             Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2762         }
2763         if (im->draw_y_grid || im->forceleftspace) {
2764             Xylabel =
2765                 gfx_get_text_width(im, 0,
2766                                    im->
2767                                    text_prop
2768                                    [TEXT_PROP_AXIS].
2769                                    font,
2770                                    im->
2771                                    text_prop
2772                                    [TEXT_PROP_AXIS].
2773                                    size, im->tabwidth, "0") * im->unitslength;
2774         }
2775     }
2776
2777     if (im->extra_flags & FULL_SIZE_MODE) {
2778         /* The actual size of the image to draw has been determined by the user.
2779          ** The graph area is the space remaining after accounting for the legend,
2780          ** the watermark, the pie chart, the axis labels, and the title.
2781          */
2782         im->xorigin = 0;
2783         im->ximg = im->xsize;
2784         im->yimg = im->ysize;
2785         im->yorigin = im->ysize;
2786         Xmain = im->ximg;
2787         Ymain = im->yimg;
2788         im->yorigin += Ytitle;
2789         /* Now calculate the total size.  Insert some spacing where
2790            desired.  im->xorigin and im->yorigin need to correspond
2791            with the lower left corner of the main graph area or, if
2792            this one is not set, the imaginary box surrounding the
2793            pie chart area. */
2794         /* Initial size calculation for the main graph area */
2795         Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2796         if (Xmain)
2797             Xmain -= Xspacing;  /* put space between main graph area and right edge */
2798         im->xorigin = Xspacing + Xylabel;
2799         /* the length of the title should not influence with width of the graph
2800            if (Xtitle > im->ximg) im->ximg = Xtitle; */
2801         if (Xvertical) {    /* unit description */
2802             Xmain -= Xvertical;
2803             im->xorigin += Xvertical;
2804         }
2805         im->xsize = Xmain;
2806         xtr(im, 0);
2807         /* The vertical size of the image is known in advance.  The main graph area
2808          ** (Ymain) and im->yorigin must be set according to the space requirements
2809          ** of the legend and the axis labels.
2810          */
2811         if (im->extra_flags & NOLEGEND) {
2812             /* set dimensions correctly if using full size mode with no legend */
2813             im->yorigin =
2814                 im->yimg -
2815                 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2816             Ymain = im->yorigin;
2817         } else {
2818             /* Determine where to place the legends onto the image.
2819              ** Set Ymain and adjust im->yorigin to match the space requirements.
2820              */
2821             if (leg_place(im, &Ymain) == -1)
2822                 return -1;
2823         }
2824
2825
2826         /* remove title space *or* some padding above the graph from the main graph area */
2827         if (Ytitle) {
2828             Ymain -= Ytitle;
2829         } else {
2830             Ymain -= 1.5 * Yspacing;
2831         }
2832
2833         /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2834         if (im->watermark[0] != '\0') {
2835             Ymain -= Ywatermark;
2836         }
2837
2838         im->ysize = Ymain;
2839     } else {            /* dimension options -width and -height refer to the dimensions of the main graph area */
2840
2841         /* The actual size of the image to draw is determined from
2842          ** several sources.  The size given on the command line is
2843          ** the graph area but we need more as we have to draw labels
2844          ** and other things outside the graph area.
2845          */
2846
2847         if (im->ylegend[0] != '\0') {
2848             Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2849         }
2850
2851
2852         if (im->title[0] != '\0') {
2853             /* The title is placed "inbetween" two text lines so it
2854              ** automatically has some vertical spacing.  The horizontal
2855              ** spacing is added here, on each side.
2856              */
2857             /* don't care for the with of the title
2858                Xtitle = gfx_get_text_width(im->canvas, 0,
2859                im->text_prop[TEXT_PROP_TITLE].font,
2860                im->text_prop[TEXT_PROP_TITLE].size,
2861                im->tabwidth,
2862                im->title, 0) + 2*Xspacing; */
2863             Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2864         }
2865
2866         if (elements) {
2867             Xmain = im->xsize;
2868             Ymain = im->ysize;
2869         }
2870         /* Now calculate the total size.  Insert some spacing where
2871            desired.  im->xorigin and im->yorigin need to correspond
2872            with the lower left corner of the main graph area or, if
2873            this one is not set, the imaginary box surrounding the
2874            pie chart area. */
2875
2876         /* The legend width cannot yet be determined, as a result we
2877          ** have problems adjusting the image to it.  For now, we just
2878          ** forget about it at all; the legend will have to fit in the
2879          ** size already allocated.
2880          */
2881         im->ximg = Xylabel + Xmain + 2 * Xspacing;
2882         if (Xmain)
2883             im->ximg += Xspacing;
2884         im->xorigin = Xspacing + Xylabel;
2885         /* the length of the title should not influence with width of the graph
2886            if (Xtitle > im->ximg) im->ximg = Xtitle; */
2887         if (Xvertical) {    /* unit description */
2888             im->ximg += Xvertical;
2889             im->xorigin += Xvertical;
2890         }
2891         xtr(im, 0);
2892         /* The vertical size is interesting... we need to compare
2893          ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with 
2894          ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2895          ** in order to start even thinking about Ylegend or Ywatermark.
2896          **
2897          ** Do it in three portions: First calculate the inner part,
2898          ** then do the legend, then adjust the total height of the img,
2899          ** adding space for a watermark if one exists;
2900          */
2901         /* reserve space for main and/or pie */
2902         im->yimg = Ymain + Yxlabel;
2903         im->yorigin = im->yimg - Yxlabel;
2904         /* reserve space for the title *or* some padding above the graph */
2905         if (Ytitle) {
2906             im->yimg += Ytitle;
2907             im->yorigin += Ytitle;
2908         } else {
2909             im->yimg += 1.5 * Yspacing;
2910             im->yorigin += 1.5 * Yspacing;
2911         }
2912         /* reserve space for padding below the graph */
2913         im->yimg += Yspacing;
2914         /* Determine where to place the legends onto the image.
2915          ** Adjust im->yimg to match the space requirements.
2916          */
2917         if (leg_place(im, 0) == -1)
2918             return -1;
2919         if (im->watermark[0] != '\0') {
2920             im->yimg += Ywatermark;
2921         }
2922     }
2923
2924     ytr(im, DNAN);
2925     return 0;
2926 }
2927
2928 static cairo_status_t cairo_output(
2929     void *closure,
2930     const unsigned char
2931     *data,
2932     unsigned int length)
2933 {
2934     image_desc_t *im = closure;
2935
2936     im->rendered_image =
2937         realloc(im->rendered_image, im->rendered_image_size + length);
2938     if (im->rendered_image == NULL)
2939         return CAIRO_STATUS_WRITE_ERROR;
2940     memcpy(im->rendered_image + im->rendered_image_size, data, length);
2941     im->rendered_image_size += length;
2942     return CAIRO_STATUS_SUCCESS;
2943 }
2944
2945 /* draw that picture thing ... */
2946 int graph_paint(
2947     image_desc_t *im)
2948 {
2949     int       i, ii;
2950     int       lazy = lazy_check(im);
2951     double    areazero = 0.0;
2952     graph_desc_t *lastgdes = NULL;
2953     infoval   info;
2954     PangoFontMap *font_map = pango_cairo_font_map_get_default();
2955
2956     /* if we are lazy and there is nothing to PRINT ... quit now */
2957     if (lazy && im->prt_c == 0)
2958         return 0;
2959     /* pull the data from the rrd files ... */
2960     if (data_fetch(im) == -1)
2961         return -1;
2962     /* evaluate VDEF and CDEF operations ... */
2963     if (data_calc(im) == -1)
2964         return -1;
2965     /* calculate and PRINT and GPRINT definitions. We have to do it at
2966      * this point because it will affect the length of the legends
2967      * if there are no graph elements we stop here ... 
2968      * if we are lazy, try to quit ... 
2969      */
2970     i = print_calc(im);
2971     if (i < 0)
2972         return -1;
2973     if ((i == 0) || lazy)
2974         return 0;
2975 /**************************************************************
2976  *** Calculating sizes and locations became a bit confusing ***
2977  *** so I moved this into a separate function.              ***
2978  **************************************************************/
2979     if (graph_size_location(im, i) == -1)
2980         return -1;
2981
2982     info.u_cnt = im->xorigin;
2983     grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
2984     info.u_cnt = im->yorigin - im->ysize;
2985     grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
2986     info.u_cnt = im->xsize;
2987     grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
2988     info.u_cnt = im->ysize;
2989     grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
2990     info.u_cnt = im->ximg;
2991     grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2992     info.u_cnt = im->yimg;
2993     grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
2994
2995     /* get actual drawing data and find min and max values */
2996     if (data_proc(im) == -1)
2997         return -1;
2998     if (!im->logarithmic) {
2999         si_unit(im);
3000     }
3001
3002     /* identify si magnitude Kilo, Mega Giga ? */
3003     if (!im->rigid && !im->logarithmic)
3004         expand_range(im);   /* make sure the upper and lower limit are
3005                                sensible values */
3006
3007     info.u_val = im->minval;
3008     grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3009     info.u_val = im->maxval;
3010     grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3011
3012     if (!calc_horizontal_grid(im))
3013         return -1;
3014     /* reset precalc */
3015     ytr(im, DNAN);
3016 /*   if (im->gridfit)
3017      apply_gridfit(im); */
3018     /* the actual graph is created by going through the individual
3019        graph elements and then drawing them */
3020     cairo_surface_destroy(im->surface);
3021     switch (im->imgformat) {
3022     case IF_PNG:
3023         im->surface =
3024             cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3025                                        im->ximg * im->zoom,
3026                                        im->yimg * im->zoom);
3027         break;
3028     case IF_PDF:
3029         im->gridfit = 0;
3030         im->surface = strlen(im->graphfile)
3031             ?
3032             cairo_pdf_surface_create_for_stream
3033             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom)
3034             : cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3035                                        im->yimg * im->zoom);
3036         break;
3037     case IF_EPS:
3038         im->gridfit = 0;
3039         im->surface = strlen(im->graphfile)
3040             ?
3041             cairo_ps_surface_create_for_stream
3042             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom)
3043             : cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3044                                       im->yimg * im->zoom);
3045         break;
3046     case IF_SVG:
3047         im->gridfit = 0;
3048         im->surface = strlen(im->graphfile)
3049             ?
3050             cairo_svg_surface_create_for_stream
3051             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom)
3052             : cairo_svg_surface_create(im->
3053                                        graphfile,
3054                                        im->
3055                                        ximg * im->zoom, im->yimg * im->zoom);
3056         cairo_svg_surface_restrict_to_version
3057             (im->surface, CAIRO_SVG_VERSION_1_1);
3058         break;
3059     };
3060     im->cr = cairo_create(im->surface);
3061     cairo_set_antialias(im->cr, im->graph_antialias);
3062     cairo_scale(im->cr, im->zoom, im->zoom);
3063     pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3064     gfx_new_area(im, 0, 0, 0, im->yimg,
3065                  im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3066     gfx_add_point(im, im->ximg, 0);
3067     gfx_close_path(im);
3068     gfx_new_area(im, im->xorigin,
3069                  im->yorigin,
3070                  im->xorigin +
3071                  im->xsize, im->yorigin,
3072                  im->xorigin +
3073                  im->xsize,
3074                  im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3075     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3076     gfx_close_path(im);
3077     if (im->minval > 0.0)
3078         areazero = im->minval;
3079     if (im->maxval < 0.0)
3080         areazero = im->maxval;
3081     for (i = 0; i < im->gdes_c; i++) {
3082         switch (im->gdes[i].gf) {
3083         case GF_CDEF:
3084         case GF_VDEF:
3085         case GF_DEF:
3086         case GF_PRINT:
3087         case GF_GPRINT:
3088         case GF_COMMENT:
3089         case GF_TEXTALIGN:
3090         case GF_HRULE:
3091         case GF_VRULE:
3092         case GF_XPORT:
3093         case GF_SHIFT:
3094             break;
3095         case GF_TICK:
3096             for (ii = 0; ii < im->xsize; ii++) {
3097                 if (!isnan(im->gdes[i].p_data[ii])
3098                     && im->gdes[i].p_data[ii] != 0.0) {
3099                     if (im->gdes[i].yrule > 0) {
3100                         gfx_line(im,
3101                                  im->xorigin + ii,
3102                                  im->yorigin,
3103                                  im->xorigin + ii,
3104                                  im->yorigin -
3105                                  im->gdes[i].yrule *
3106                                  im->ysize, 1.0, im->gdes[i].col);
3107                     } else if (im->gdes[i].yrule < 0) {
3108                         gfx_line(im,
3109                                  im->xorigin + ii,
3110                                  im->yorigin - im->ysize,
3111                                  im->xorigin + ii,
3112                                  im->yorigin - (1 -
3113                                                 im->gdes[i].
3114                                                 yrule) *
3115                                  im->ysize, 1.0, im->gdes[i].col);
3116                     }
3117                 }
3118             }
3119             break;
3120         case GF_LINE:
3121         case GF_AREA:
3122             /* fix data points at oo and -oo */
3123             for (ii = 0; ii < im->xsize; ii++) {
3124                 if (isinf(im->gdes[i].p_data[ii])) {
3125                     if (im->gdes[i].p_data[ii] > 0) {
3126                         im->gdes[i].p_data[ii] = im->maxval;
3127                     } else {
3128                         im->gdes[i].p_data[ii] = im->minval;
3129                     }
3130
3131                 }
3132             }           /* for */
3133
3134             /* *******************************************************
3135                a           ___. (a,t) 
3136                |   |    ___
3137                ____|   |   |   |
3138                |       |___|
3139                -------|--t-1--t--------------------------------      
3140
3141                if we know the value at time t was a then 
3142                we draw a square from t-1 to t with the value a.
3143
3144                ********************************************************* */
3145             if (im->gdes[i].col.alpha != 0.0) {
3146                 /* GF_LINE and friend */
3147                 if (im->gdes[i].gf == GF_LINE) {
3148                     double    last_y = 0.0;
3149                     int       draw_on = 0;
3150
3151                     cairo_save(im->cr);
3152                     cairo_new_path(im->cr);
3153                     cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3154                     if (im->gdes[i].dash) {
3155                         cairo_set_dash(im->cr,
3156                                        im->gdes[i].p_dashes,
3157                                        im->gdes[i].ndash, im->gdes[i].offset);
3158                     }
3159
3160                     for (ii = 1; ii < im->xsize; ii++) {
3161                         if (isnan(im->gdes[i].p_data[ii])
3162                             || (im->slopemode == 1
3163                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3164                             draw_on = 0;
3165                             continue;
3166                         }
3167                         if (draw_on == 0) {
3168                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3169                             if (im->slopemode == 0) {
3170                                 double    x = ii - 1 + im->xorigin;
3171                                 double    y = last_y;
3172
3173                                 gfx_line_fit(im, &x, &y);
3174                                 cairo_move_to(im->cr, x, y);
3175                                 x = ii + im->xorigin;
3176                                 y = last_y;
3177                                 gfx_line_fit(im, &x, &y);
3178                                 cairo_line_to(im->cr, x, y);
3179                             } else {
3180                                 double    x = ii - 1 + im->xorigin;
3181                                 double    y =
3182                                     ytr(im, im->gdes[i].p_data[ii - 1]);
3183                                 gfx_line_fit(im, &x, &y);
3184                                 cairo_move_to(im->cr, x, y);
3185                                 x = ii + im->xorigin;
3186                                 y = last_y;
3187                                 gfx_line_fit(im, &x, &y);
3188                                 cairo_line_to(im->cr, x, y);
3189                             }
3190                             draw_on = 1;
3191                         } else {
3192                             double    x1 = ii + im->xorigin;
3193                             double    y1 = ytr(im, im->gdes[i].p_data[ii]);
3194
3195                             if (im->slopemode == 0
3196                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3197                                 double    x = ii - 1 + im->xorigin;
3198                                 double    y = y1;
3199
3200                                 gfx_line_fit(im, &x, &y);
3201                                 cairo_line_to(im->cr, x, y);
3202                             };
3203                             last_y = y1;
3204                             gfx_line_fit(im, &x1, &y1);
3205                             cairo_line_to(im->cr, x1, y1);
3206                         };
3207                     }
3208                     cairo_set_source_rgba(im->cr,
3209                                           im->gdes[i].
3210                                           col.red,
3211                                           im->gdes[i].
3212                                           col.green,
3213                                           im->gdes[i].
3214                                           col.blue, im->gdes[i].col.alpha);
3215                     cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3216                     cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3217                     cairo_stroke(im->cr);
3218                     cairo_restore(im->cr);
3219                 } else {
3220                     int       idxI = -1;
3221                     double   *foreY =
3222                         (double *) malloc(sizeof(double) * im->xsize * 2);
3223                     double   *foreX =
3224                         (double *) malloc(sizeof(double) * im->xsize * 2);
3225                     double   *backY =
3226                         (double *) malloc(sizeof(double) * im->xsize * 2);
3227                     double   *backX =
3228                         (double *) malloc(sizeof(double) * im->xsize * 2);
3229                     int       drawem = 0;
3230
3231                     for (ii = 0; ii <= im->xsize; ii++) {
3232                         double    ybase, ytop;
3233
3234                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3235                             int       cntI = 1;
3236                             int       lastI = 0;
3237
3238                             while (cntI < idxI
3239                                    &&
3240                                    AlmostEqual2sComplement(foreY
3241                                                            [lastI],
3242                                                            foreY[cntI], 4)
3243                                    &&
3244                                    AlmostEqual2sComplement(foreY
3245                                                            [lastI],
3246                                                            foreY
3247                                                            [cntI + 1], 4)) {
3248                                 cntI++;
3249                             }
3250                             gfx_new_area(im,
3251                                          backX[0], backY[0],
3252                                          foreX[0], foreY[0],
3253                                          foreX[cntI],
3254                                          foreY[cntI], im->gdes[i].col);
3255                             while (cntI < idxI) {
3256                                 lastI = cntI;
3257                                 cntI++;
3258                                 while (cntI < idxI
3259                                        &&
3260                                        AlmostEqual2sComplement(foreY
3261                                                                [lastI],
3262                                                                foreY[cntI], 4)
3263                                        &&
3264                                        AlmostEqual2sComplement(foreY
3265                                                                [lastI],
3266                                                                foreY
3267                                                                [cntI
3268                                                                 + 1], 4)) {
3269                                     cntI++;
3270                                 }
3271                                 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3272                             }
3273                             gfx_add_point(im, backX[idxI], backY[idxI]);
3274                             while (idxI > 1) {
3275                                 lastI = idxI;
3276                                 idxI--;
3277                                 while (idxI > 1
3278                                        &&
3279                                        AlmostEqual2sComplement(backY
3280                                                                [lastI],
3281                                                                backY[idxI], 4)
3282                                        &&
3283                                        AlmostEqual2sComplement(backY
3284                                                                [lastI],
3285                                                                backY
3286                                                                [idxI
3287                                                                 - 1], 4)) {
3288                                     idxI--;
3289                                 }
3290                                 gfx_add_point(im, backX[idxI], backY[idxI]);
3291                             }
3292                             idxI = -1;
3293                             drawem = 0;
3294                             gfx_close_path(im);
3295                         }
3296                         if (drawem != 0) {
3297                             drawem = 0;
3298                             idxI = -1;
3299                         }
3300                         if (ii == im->xsize)
3301                             break;
3302                         if (im->slopemode == 0 && ii == 0) {
3303                             continue;
3304                         }
3305                         if (isnan(im->gdes[i].p_data[ii])) {
3306                             drawem = 1;
3307                             continue;
3308                         }
3309                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3310                         if (lastgdes && im->gdes[i].stack) {
3311                             ybase = ytr(im, lastgdes->p_data[ii]);
3312                         } else {
3313                             ybase = ytr(im, areazero);
3314                         }
3315                         if (ybase == ytop) {
3316                             drawem = 1;
3317                             continue;
3318                         }
3319
3320                         if (ybase > ytop) {
3321                             double    extra = ytop;
3322
3323                             ytop = ybase;
3324                             ybase = extra;
3325                         }
3326                         if (im->slopemode == 0) {
3327                             backY[++idxI] = ybase - 0.2;
3328                             backX[idxI] = ii + im->xorigin - 1;
3329                             foreY[idxI] = ytop + 0.2;
3330                             foreX[idxI] = ii + im->xorigin - 1;
3331                         }
3332                         backY[++idxI] = ybase - 0.2;
3333                         backX[idxI] = ii + im->xorigin;
3334                         foreY[idxI] = ytop + 0.2;
3335                         foreX[idxI] = ii + im->xorigin;
3336                     }
3337                     /* close up any remaining area */
3338                     free(foreY);
3339                     free(foreX);
3340                     free(backY);
3341                     free(backX);
3342                 }       /* else GF_LINE */
3343             }
3344             /* if color != 0x0 */
3345             /* make sure we do not run into trouble when stacking on NaN */
3346             for (ii = 0; ii < im->xsize; ii++) {
3347                 if (isnan(im->gdes[i].p_data[ii])) {
3348                     if (lastgdes && (im->gdes[i].stack)) {
3349                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3350                     } else {
3351                         im->gdes[i].p_data[ii] = areazero;
3352                     }
3353                 }
3354             }
3355             lastgdes = &(im->gdes[i]);
3356             break;
3357         case GF_STACK:
3358             rrd_set_error
3359                 ("STACK should already be turned into LINE or AREA here");
3360             return -1;
3361             break;
3362         }               /* switch */
3363     }
3364
3365     /* grid_paint also does the text */
3366     if (!(im->extra_flags & ONLY_GRAPH))
3367         grid_paint(im);
3368     if (!(im->extra_flags & ONLY_GRAPH))
3369         axis_paint(im);
3370     /* the RULES are the last thing to paint ... */
3371     for (i = 0; i < im->gdes_c; i++) {
3372
3373         switch (im->gdes[i].gf) {
3374         case GF_HRULE:
3375             if (im->gdes[i].yrule >= im->minval
3376                 && im->gdes[i].yrule <= im->maxval) {
3377                 cairo_save(im->cr);
3378                 if (im->gdes[i].dash) {
3379                     cairo_set_dash(im->cr,
3380                                    im->gdes[i].p_dashes,
3381                                    im->gdes[i].ndash, im->gdes[i].offset);
3382                 }
3383                 gfx_line(im, im->xorigin,
3384                          ytr(im, im->gdes[i].yrule),
3385                          im->xorigin + im->xsize,
3386                          ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3387                 cairo_stroke(im->cr);
3388                 cairo_restore(im->cr);
3389             }
3390             break;
3391         case GF_VRULE:
3392             if (im->gdes[i].xrule >= im->start
3393                 && im->gdes[i].xrule <= im->end) {
3394                 cairo_save(im->cr);
3395                 if (im->gdes[i].dash) {
3396                     cairo_set_dash(im->cr,
3397                                    im->gdes[i].p_dashes,
3398                                    im->gdes[i].ndash, im->gdes[i].offset);
3399                 }
3400                 gfx_line(im,
3401                          xtr(im, im->gdes[i].xrule),
3402                          im->yorigin, xtr(im,
3403                                           im->
3404                                           gdes[i].
3405                                           xrule),
3406                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3407                 cairo_stroke(im->cr);
3408                 cairo_restore(im->cr);
3409             }
3410             break;
3411         default:
3412             break;
3413         }
3414     }
3415
3416
3417     switch (im->imgformat) {
3418     case IF_PNG:
3419     {
3420         cairo_status_t status;
3421
3422         status = strlen(im->graphfile) ?
3423             cairo_surface_write_to_png(im->surface, im->graphfile)
3424             : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3425                                                 im);
3426
3427         if (status != CAIRO_STATUS_SUCCESS) {
3428             rrd_set_error("Could not save png to '%s'", im->graphfile);
3429             return 1;
3430         }
3431     }
3432         break;
3433     default:
3434         if (strlen(im->graphfile)) {
3435             cairo_show_page(im->cr);
3436         } else {
3437             cairo_surface_finish(im->surface);
3438         }
3439         break;
3440     }
3441
3442     return 0;
3443 }
3444
3445
3446 /*****************************************************
3447  * graph stuff 
3448  *****************************************************/
3449
3450 int gdes_alloc(
3451     image_desc_t *im)
3452 {
3453
3454     im->gdes_c++;
3455     if ((im->gdes = (graph_desc_t *)
3456          rrd_realloc(im->gdes, (im->gdes_c)
3457                      * sizeof(graph_desc_t))) == NULL) {
3458         rrd_set_error("realloc graph_descs");
3459         return -1;
3460     }
3461
3462
3463     im->gdes[im->gdes_c - 1].step = im->step;
3464     im->gdes[im->gdes_c - 1].step_orig = im->step;
3465     im->gdes[im->gdes_c - 1].stack = 0;
3466     im->gdes[im->gdes_c - 1].linewidth = 0;
3467     im->gdes[im->gdes_c - 1].debug = 0;
3468     im->gdes[im->gdes_c - 1].start = im->start;
3469     im->gdes[im->gdes_c - 1].start_orig = im->start;
3470     im->gdes[im->gdes_c - 1].end = im->end;
3471     im->gdes[im->gdes_c - 1].end_orig = im->end;
3472     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3473     im->gdes[im->gdes_c - 1].data = NULL;
3474     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3475     im->gdes[im->gdes_c - 1].data_first = 0;
3476     im->gdes[im->gdes_c - 1].p_data = NULL;
3477     im->gdes[im->gdes_c - 1].rpnp = NULL;
3478     im->gdes[im->gdes_c - 1].p_dashes = NULL;
3479     im->gdes[im->gdes_c - 1].shift = 0.0;
3480     im->gdes[im->gdes_c - 1].dash = 0;
3481     im->gdes[im->gdes_c - 1].ndash = 0;
3482     im->gdes[im->gdes_c - 1].offset = 0;
3483     im->gdes[im->gdes_c - 1].col.red = 0.0;
3484     im->gdes[im->gdes_c - 1].col.green = 0.0;
3485     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3486     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3487     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3488     im->gdes[im->gdes_c - 1].format[0] = '\0';
3489     im->gdes[im->gdes_c - 1].strftm = 0;
3490     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3491     im->gdes[im->gdes_c - 1].ds = -1;
3492     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3493     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3494     im->gdes[im->gdes_c - 1].yrule = DNAN;
3495     im->gdes[im->gdes_c - 1].xrule = 0;
3496     return 0;
3497 }
3498
3499 /* copies input untill the first unescaped colon is found
3500    or until input ends. backslashes have to be escaped as well */
3501 int scan_for_col(
3502     const char *const input,
3503     int len,
3504     char *const output)
3505 {
3506     int       inp, outp = 0;
3507
3508     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3509         if (input[inp] == '\\'
3510             && input[inp + 1] != '\0'
3511             && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3512             output[outp++] = input[++inp];
3513         } else {
3514             output[outp++] = input[inp];
3515         }
3516     }
3517     output[outp] = '\0';
3518     return inp;
3519 }
3520
3521 /* Now just a wrapper around rrd_graph_v */
3522 int rrd_graph(
3523     int argc,
3524     char **argv,
3525     char ***prdata,
3526     int *xsize,
3527     int *ysize,
3528     FILE * stream,
3529     double *ymin,
3530     double *ymax)
3531 {
3532     int       prlines = 0;
3533     info_t   *grinfo = NULL;
3534     info_t   *walker;
3535
3536     grinfo = rrd_graph_v(argc, argv);
3537     if (grinfo == NULL)
3538         return -1;
3539     walker = grinfo;
3540     (*prdata) = NULL;
3541     while (walker) {
3542         if (strcmp(walker->key, "image_info") == 0) {
3543             prlines++;
3544             if (((*prdata) =
3545                  rrd_realloc((*prdata),
3546                              (prlines + 1) * sizeof(char *))) == NULL) {
3547                 rrd_set_error("realloc prdata");
3548                 return 0;
3549             }
3550             /* imginfo goes to position 0 in the prdata array */
3551             (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3552                                              + 2) * sizeof(char));
3553             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3554             (*prdata)[prlines] = NULL;
3555         }
3556         /* skip anything else */
3557         walker = walker->next;
3558     }
3559     walker = grinfo;
3560     while (walker) {
3561         if (strcmp(walker->key, "image_width") == 0) {
3562             *xsize = walker->value.u_int;
3563         } else if (strcmp(walker->key, "image_height") == 0) {
3564             *ysize = walker->value.u_int;
3565         } else if (strcmp(walker->key, "value_min") == 0) {
3566             *ymin = walker->value.u_val;
3567         } else if (strcmp(walker->key, "value_max") == 0) {
3568             *ymax = walker->value.u_val;
3569         } else if (strncmp(walker->key, "print", 6) == 0) { /* keys are prdate[0..] */
3570             prlines++;
3571             if (((*prdata) =
3572                  rrd_realloc((*prdata),
3573                              (prlines + 1) * sizeof(char *))) == NULL) {
3574                 rrd_set_error("realloc prdata");
3575                 return 0;
3576             }
3577             (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3578                                              + 2) * sizeof(char));
3579             (*prdata)[prlines] = NULL;
3580             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3581         } else if (strcmp(walker->key, "image") == 0) {
3582             fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3583                    (stream ? stream : stdout));
3584         }
3585         /* skip anything else */
3586         walker = walker->next;
3587     }
3588     info_free(grinfo);
3589     return 0;
3590 }
3591
3592
3593 /* Some surgery done on this function, it became ridiculously big.
3594 ** Things moved:
3595 ** - initializing     now in rrd_graph_init()
3596 ** - options parsing  now in rrd_graph_options()
3597 ** - script parsing   now in rrd_graph_script()
3598 */
3599 info_t   *rrd_graph_v(
3600     int argc,
3601     char **argv)
3602 {
3603     image_desc_t im;
3604     info_t   *grinfo;
3605
3606     rrd_graph_init(&im);
3607     /* a dummy surface so that we can measure text sizes for placements */
3608     im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3609     im.cr = cairo_create(im.surface);
3610     rrd_graph_options(argc, argv, &im);
3611     if (rrd_test_error()) {
3612         info_free(im.grinfo);
3613         im_free(&im);
3614         return NULL;
3615     }
3616
3617     if (optind >= argc) {
3618         info_free(im.grinfo);
3619         im_free(&im);
3620         rrd_set_error("missing filename");
3621         return NULL;
3622     }
3623
3624     if (strlen(argv[optind]) >= MAXPATH) {
3625         rrd_set_error("filename (including path) too long");
3626         info_free(im.grinfo);
3627         im_free(&im);
3628         return NULL;
3629     }
3630
3631     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3632     im.graphfile[MAXPATH - 1] = '\0';
3633
3634     if (strcmp(im.graphfile, "-") == 0) {
3635         im.graphfile[0] = '\0';
3636     }
3637
3638     rrd_graph_script(argc, argv, &im, 1);
3639     if (rrd_test_error()) {
3640         info_free(im.grinfo);
3641         im_free(&im);
3642         return NULL;
3643     }
3644
3645     /* Everything is now read and the actual work can start */
3646
3647     if (graph_paint(&im) == -1) {
3648         info_free(im.grinfo);
3649         im_free(&im);
3650         return NULL;
3651     }
3652
3653
3654     /* The image is generated and needs to be output.
3655      ** Also, if needed, print a line with information about the image.
3656      */
3657
3658     if (im.imginfo) {
3659         infoval   info;
3660
3661         info.u_str =
3662             sprintf_alloc(im.imginfo,
3663                           im.graphfile,
3664                           (long) (im.zoom *
3665                                   im.ximg), (long) (im.zoom * im.yimg));
3666         grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3667         free(info.u_str);
3668     }
3669     if (im.rendered_image) {
3670         infoval   img;
3671
3672         img.u_blo.size = im.rendered_image_size;
3673         img.u_blo.ptr = im.rendered_image;
3674         grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3675     }
3676     grinfo = im.grinfo;
3677     im_free(&im);
3678     return grinfo;
3679 }
3680
3681 void rrd_graph_init(
3682     image_desc_t
3683     *im)
3684 {
3685     unsigned int i;
3686
3687 #ifdef HAVE_TZSET
3688     tzset();
3689 #endif
3690 #ifdef HAVE_SETLOCALE
3691     setlocale(LC_TIME, "");
3692 #ifdef HAVE_MBSTOWCS
3693     setlocale(LC_CTYPE, "");
3694 #endif
3695 #endif
3696     im->base = 1000;
3697     im->cr = NULL;
3698     im->draw_x_grid = 1;
3699     im->draw_y_grid = 1;
3700     im->extra_flags = 0;
3701     im->font_options = cairo_font_options_create();
3702     im->forceleftspace = 0;
3703     im->gdes_c = 0;
3704     im->gdes = NULL;
3705     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3706     im->grid_dash_off = 1;
3707     im->grid_dash_on = 1;
3708     im->gridfit = 1;
3709     im->grinfo = (info_t *) NULL;
3710     im->grinfo_current = (info_t *) NULL;
3711     im->imgformat = IF_PNG;
3712     im->imginfo = NULL;
3713     im->lazy = 0;
3714     im->logarithmic = 0;
3715     im->maxval = DNAN;
3716     im->minval = 0;
3717     im->minval = DNAN;
3718     im->prt_c = 0;
3719     im->rigid = 0;
3720     im->rendered_image_size = 0;
3721     im->rendered_image = NULL;
3722     im->slopemode = 0;
3723     im->step = 0;
3724     im->surface = NULL;
3725     im->symbol = ' ';
3726     im->tabwidth = 40.0;
3727     im->title[0] = '\0';
3728     im->unitsexponent = 9999;
3729     im->unitslength = 6;
3730     im->viewfactor = 1.0;
3731     im->watermark[0] = '\0';
3732     im->ximg = 0;
3733     im->xlab_user.minsec = -1;
3734     im->xorigin = 0;
3735     im->xsize = 400;
3736     im->ygridstep = DNAN;
3737     im->yimg = 0;
3738     im->ylegend[0] = '\0';
3739     im->yorigin = 0;
3740     im->ysize = 100;
3741     im->zoom = 1;
3742     cairo_font_options_set_hint_style
3743         (im->font_options, CAIRO_HINT_STYLE_FULL);
3744     cairo_font_options_set_hint_metrics
3745         (im->font_options, CAIRO_HINT_METRICS_ON);
3746     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3747     for (i = 0; i < DIM(graph_col); i++)
3748         im->graph_col[i] = graph_col[i];
3749 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3750     {
3751         char     *windir;
3752         char      rrd_win_default_font[1000];
3753
3754         windir = getenv("windir");
3755         /* %windir% is something like D:\windows or C:\winnt */
3756         if (windir != NULL) {
3757             strncpy(rrd_win_default_font, windir, 500);
3758             rrd_win_default_font[500] = '\0';
3759             strcat(rrd_win_default_font, "\\fonts\\");
3760             strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3761             for (i = 0; i < DIM(text_prop); i++) {
3762                 strncpy(text_prop[i].font,
3763                         rrd_win_default_font, sizeof(text_prop[i].font) - 1);
3764                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3765             }
3766         }
3767     }
3768 #endif
3769     {
3770         char     *deffont;
3771
3772         deffont = getenv("RRD_DEFAULT_FONT");
3773         if (deffont != NULL) {
3774             for (i = 0; i < DIM(text_prop); i++) {
3775                 strncpy(text_prop[i].font, deffont,
3776                         sizeof(text_prop[i].font) - 1);
3777                 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3778             }
3779         }
3780     }
3781     for (i = 0; i < DIM(text_prop); i++) {
3782         im->text_prop[i].size = text_prop[i].size;
3783         strcpy(im->text_prop[i].font, text_prop[i].font);
3784     }
3785 }
3786
3787 void rrd_graph_options(
3788     int argc,
3789     char *argv[],
3790     image_desc_t
3791     *im)
3792 {
3793     int       stroff;
3794     char     *parsetime_error = NULL;
3795     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3796     time_t    start_tmp = 0, end_tmp = 0;
3797     long      long_tmp;
3798     struct rrd_time_value start_tv, end_tv;
3799     long unsigned int color;
3800     char     *old_locale = "";
3801
3802     /* defines for long options without a short equivalent. should be bytes,
3803        and may not collide with (the ASCII value of) short options */
3804 #define LONGOPT_UNITS_SI 255
3805     struct option long_options[] = {
3806         {
3807          "start", required_argument, 0, 's'}, {
3808                                                "end", required_argument, 0,
3809                                                'e'}, {
3810                                                       "x-grid",
3811                                                       required_argument, 0,
3812                                                       'x'}, {
3813                                                              "y-grid",
3814                                                              required_argument,
3815                                                              0, 'y'}, {
3816                                                                        "vertical-label",
3817                                                                        required_argument,
3818                                                                        0,
3819                                                                        'v'}, {
3820                                                                               "width",
3821                                                                               required_argument,
3822                                                                               0,
3823                                                                               'w'},
3824         {
3825          "height", required_argument, 0, 'h'}, {
3826                                                 "full-size-mode", no_argument,
3827                                                 0, 'D'}, {
3828                                                           "interlaced",
3829                                                           no_argument, 0,
3830                                                           'i'}, {
3831                                                                  "upper-limit",
3832                                                                  required_argument,
3833                                                                  0,
3834                                                                  'u'}, {
3835                                                                         "lower-limit",
3836                                                                         required_argument,
3837                                                                         0,
3838                                                                         'l'}, {
3839                                                                                "rigid",
3840                                                                                no_argument,
3841                                                                                0,
3842                                                                                'r'},
3843         {
3844          "base", required_argument, 0, 'b'}, {
3845                                               "logarithmic", no_argument, 0,
3846                                               'o'}, {
3847                                                      "color",
3848                                                      required_argument, 0,
3849                                                      'c'}, {
3850                                                             "font",
3851                                                             required_argument,
3852                                                             0, 'n'}, {
3853                                                                       "title",
3854                                                                       required_argument,
3855                                                                       0, 't'},
3856         {
3857          "imginfo", required_argument, 0, 'f'}, {
3858                                                  "imgformat",
3859                                                  required_argument, 0, 'a'}, {
3860                                                                               "lazy",
3861                                                                               no_argument,
3862                                                                               0,
3863                                                                               'z'},
3864         {
3865          "zoom", required_argument, 0, 'm'}, {
3866                                               "no-legend", no_argument, 0,
3867                                               'g'}, {
3868                                                      "force-rules-legend",
3869                                                      no_argument,
3870                                                      0, 'F'}, {
3871                                                                "only-graph",
3872                                                                no_argument, 0,
3873                                                                'j'}, {
3874                                                                       "alt-y-grid",
3875                                                                       no_argument,
3876                                                                       0, 'Y'},
3877         {
3878          "no-minor", no_argument, 0, 'I'}, {
3879                                             "slope-mode", no_argument, 0,
3880                                             'E'}, {
3881                                                    "alt-autoscale",
3882                                                    no_argument, 0, 'A'}, {
3883                                                                           "alt-autoscale-min",
3884                                                                           no_argument,
3885                                                                           0,
3886                                                                           'J'}, {
3887                                                                                  "alt-autoscale-max",
3888                                                                                  no_argument,
3889                                                                                  0,
3890                                                                                  'M'}, {
3891                                                                                         "no-gridfit",
3892                                                                                         no_argument,
3893                                                                                         0,
3894                                                                                         'N'},
3895         {
3896          "units-exponent", required_argument,
3897          0, 'X'}, {
3898                    "units-length", required_argument,
3899                    0, 'L'}, {
3900                              "units", required_argument, 0,
3901                              LONGOPT_UNITS_SI}, {
3902                                                  "step", required_argument, 0,
3903                                                  'S'}, {
3904                                                         "tabwidth",
3905                                                         required_argument, 0,
3906                                                         'T'}, {
3907                                                                "font-render-mode",
3908                                                                required_argument,
3909                                                                0, 'R'}, {
3910                                                                          "graph-render-mode",
3911                                                                          required_argument,
3912                                                                          0,
3913                                                                          'G'},
3914         {
3915          "font-smoothing-threshold",
3916          required_argument, 0, 'B'}, {
3917                                       "watermark", required_argument, 0, 'W'}, {
3918                                                                                 "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 */
3919         {
3920          0, 0, 0, 0}
3921     };
3922     optind = 0;
3923     opterr = 0;         /* initialize getopt */
3924     parsetime("end-24h", &start_tv);
3925     parsetime("now", &end_tv);
3926     while (1) {
3927         int       option_index = 0;
3928         int       opt;
3929         int       col_start, col_end;
3930
3931         opt = getopt_long(argc, argv,
3932                           "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",
3933                           long_options, &option_index);
3934         if (opt == EOF)
3935             break;
3936         switch (opt) {
3937         case 'I':
3938             im->extra_flags |= NOMINOR;
3939             break;
3940         case 'Y':
3941             im->extra_flags |= ALTYGRID;
3942             break;
3943         case 'A':
3944             im->extra_flags |= ALTAUTOSCALE;
3945             break;
3946         case 'J':
3947             im->extra_flags |= ALTAUTOSCALE_MIN;
3948             break;
3949         case 'M':
3950             im->extra_flags |= ALTAUTOSCALE_MAX;
3951             break;
3952         case 'j':
3953             im->extra_flags |= ONLY_GRAPH;
3954             break;
3955         case 'g':
3956             im->extra_flags |= NOLEGEND;
3957             break;
3958         case 'F':
3959             im->extra_flags |= FORCE_RULES_LEGEND;
3960             break;
3961         case LONGOPT_UNITS_SI:
3962             if (im->extra_flags & FORCE_UNITS) {
3963                 rrd_set_error("--units can only be used once!");
3964                 setlocale(LC_NUMERIC, old_locale);
3965                 return;
3966             }
3967             if (strcmp(optarg, "si") == 0)
3968                 im->extra_flags |= FORCE_UNITS_SI;
3969             else {
3970                 rrd_set_error("invalid argument for --units: %s", optarg);
3971                 return;
3972             }
3973             break;
3974         case 'X':
3975             im->unitsexponent = atoi(optarg);
3976             break;
3977         case 'L':
3978             im->unitslength = atoi(optarg);
3979             im->forceleftspace = 1;
3980             break;
3981         case 'T':
3982             old_locale = setlocale(LC_NUMERIC, "C");
3983             im->tabwidth = atof(optarg);
3984             setlocale(LC_NUMERIC, old_locale);
3985             break;
3986         case 'S':
3987             old_locale = setlocale(LC_NUMERIC, "C");
3988             im->step = atoi(optarg);
3989             setlocale(LC_NUMERIC, old_locale);
3990             break;
3991         case 'N':
3992             im->gridfit = 0;
3993             break;
3994         case 's':
3995             if ((parsetime_error = parsetime(optarg, &start_tv))) {
3996                 rrd_set_error("start time: %s", parsetime_error);
3997                 return;
3998             }
3999             break;
4000         case 'e':
4001             if ((parsetime_error = parsetime(optarg, &end_tv))) {
4002                 rrd_set_error("end time: %s", parsetime_error);
4003                 return;
4004             }
4005             break;
4006         case 'x':
4007             if (strcmp(optarg, "none") == 0) {
4008                 im->draw_x_grid = 0;
4009                 break;
4010             };
4011             if (sscanf(optarg,
4012                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4013                        scan_gtm,
4014                        &im->xlab_user.gridst,
4015                        scan_mtm,
4016                        &im->xlab_user.mgridst,
4017                        scan_ltm,
4018                        &im->xlab_user.labst,
4019                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4020                 strncpy(im->xlab_form, optarg + stroff,
4021                         sizeof(im->xlab_form) - 1);
4022                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4023                 if ((int)
4024                     (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4025                     rrd_set_error("unknown keyword %s", scan_gtm);
4026                     return;
4027                 } else if ((int)
4028                            (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4029                            == -1) {
4030                     rrd_set_error("unknown keyword %s", scan_mtm);
4031                     return;
4032                 } else if ((int)
4033                            (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4034                     rrd_set_error("unknown keyword %s", scan_ltm);
4035                     return;
4036                 }
4037                 im->xlab_user.minsec = 1;
4038                 im->xlab_user.stst = im->xlab_form;
4039             } else {
4040                 rrd_set_error("invalid x-grid format");
4041                 return;
4042             }
4043             break;
4044         case 'y':
4045
4046             if (strcmp(optarg, "none") == 0) {
4047                 im->draw_y_grid = 0;
4048                 break;
4049             };
4050             old_locale = setlocale(LC_NUMERIC, "C");
4051             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4052                 setlocale(LC_NUMERIC, old_locale);
4053                 if (im->ygridstep <= 0) {
4054                     rrd_set_error("grid step must be > 0");
4055                     return;
4056                 } else if (im->ylabfact < 1) {
4057                     rrd_set_error("label factor must be > 0");
4058                     return;
4059                 }
4060             } else {
4061                 setlocale(LC_NUMERIC, old_locale);
4062                 rrd_set_error("invalid y-grid format");
4063                 return;
4064             }
4065             break;
4066         case 'v':
4067             strncpy(im->ylegend, optarg, 150);
4068             im->ylegend[150] = '\0';
4069             break;
4070         case 'u':
4071             old_locale = setlocale(LC_NUMERIC, "C");
4072             im->maxval = atof(optarg);
4073             setlocale(LC_NUMERIC, old_locale);
4074             break;
4075         case 'l':
4076             old_locale = setlocale(LC_NUMERIC, "C");
4077             im->minval = atof(optarg);
4078             setlocale(LC_NUMERIC, old_locale);
4079             break;
4080         case 'b':
4081             im->base = atol(optarg);
4082             if (im->base != 1024 && im->base != 1000) {
4083                 rrd_set_error
4084                     ("the only sensible value for base apart from 1000 is 1024");
4085                 return;
4086             }
4087             break;
4088         case 'w':
4089             long_tmp = atol(optarg);
4090             if (long_tmp < 10) {
4091                 rrd_set_error("width below 10 pixels");
4092                 return;
4093             }
4094             im->xsize = long_tmp;
4095             break;
4096         case 'h':
4097             long_tmp = atol(optarg);
4098             if (long_tmp < 10) {
4099                 rrd_set_error("height below 10 pixels");
4100                 return;
4101             }
4102             im->ysize = long_tmp;
4103             break;
4104         case 'D':
4105             im->extra_flags |= FULL_SIZE_MODE;
4106             break;
4107         case 'i':
4108             /* interlaced png not supported at the moment */
4109             break;
4110         case 'r':
4111             im->rigid = 1;
4112             break;
4113         case 'f':
4114             im->imginfo = optarg;
4115             break;
4116         case 'a':
4117             if ((int)
4118                 (im->imgformat = if_conv(optarg)) == -1) {
4119                 rrd_set_error("unsupported graphics format '%s'", optarg);
4120                 return;
4121             }
4122             break;
4123         case 'z':
4124             im->lazy = 1;
4125             break;
4126         case 'E':
4127             im->slopemode = 1;
4128             break;
4129         case 'o':
4130             im->logarithmic = 1;
4131             break;
4132         case 'c':
4133             if (sscanf(optarg,
4134                        "%10[A-Z]#%n%8lx%n",
4135                        col_nam, &col_start, &color, &col_end) == 2) {
4136                 int       ci;
4137                 int       col_len = col_end - col_start;
4138
4139                 switch (col_len) {
4140                 case 3:
4141                     color =
4142                         (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4143                                                          0x011000) |
4144                          ((color & 0x00F)
4145                           * 0x001100)
4146                          | 0x000000FF);
4147                     break;
4148                 case 4:
4149                     color =
4150                         (((color & 0xF000) *
4151                           0x11000) | ((color & 0x0F00) *
4152                                       0x01100) | ((color &
4153                                                    0x00F0) *
4154                                                   0x00110) |
4155                          ((color & 0x000F) * 0x00011)
4156                         );
4157                     break;
4158                 case 6:
4159                     color = (color << 8) + 0xff /* shift left by 8 */ ;
4160                     break;
4161                 case 8:
4162                     break;
4163                 default:
4164                     rrd_set_error("the color format is #RRGGBB[AA]");
4165                     return;
4166                 }
4167                 if ((ci = grc_conv(col_nam)) != -1) {
4168                     im->graph_col[ci] = gfx_hex_to_col(color);
4169                 } else {
4170                     rrd_set_error("invalid color name '%s'", col_nam);
4171                     return;
4172                 }
4173             } else {
4174                 rrd_set_error("invalid color def format");
4175                 return;
4176             }
4177             break;
4178         case 'n':{
4179             char      prop[15];
4180             double    size = 1;
4181             int       end;
4182
4183             old_locale = setlocale(LC_NUMERIC, "C");
4184             if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4185                 int       sindex, propidx;
4186
4187                 setlocale(LC_NUMERIC, old_locale);
4188                 if ((sindex = text_prop_conv(prop)) != -1) {
4189                     for (propidx = sindex;
4190                          propidx < TEXT_PROP_LAST; propidx++) {
4191                         if (size > 0) {
4192                             im->text_prop[propidx].size = size;
4193                         }
4194                         if ((int) strlen(prop) > end) {
4195                             if (prop[end] == ':') {
4196                                 strncpy(im->text_prop[propidx].font,
4197                                         prop + end + 1, 255);
4198                                 im->text_prop[propidx].font[255] = '\0';
4199                             } else {
4200                                 rrd_set_error
4201                                     ("expected after font size in '%s'",
4202                                      prop);
4203                                 return;
4204                             }
4205                         }
4206                         if (propidx == sindex && sindex != 0)
4207                             break;
4208                     }
4209                 } else {
4210                     rrd_set_error("invalid fonttag '%s'", prop);
4211                     return;
4212                 }
4213             } else {
4214                 setlocale(LC_NUMERIC, old_locale);
4215                 rrd_set_error("invalid text property format");
4216                 return;
4217             }
4218             break;
4219         }
4220         case 'm':
4221             old_locale = setlocale(LC_NUMERIC, "C");
4222             im->zoom = atof(optarg);
4223             setlocale(LC_NUMERIC, old_locale);
4224             if (im->zoom <= 0.0) {
4225                 rrd_set_error("zoom factor must be > 0");
4226                 return;
4227             }
4228             break;
4229         case 't':
4230             strncpy(im->title, optarg, 150);
4231             im->title[150] = '\0';
4232             break;
4233         case 'R':
4234             if (strcmp(optarg, "normal") == 0) {
4235                 cairo_font_options_set_antialias
4236                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4237                 cairo_font_options_set_hint_style
4238                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4239             } else if (strcmp(optarg, "light") == 0) {
4240                 cairo_font_options_set_antialias
4241                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4242                 cairo_font_options_set_hint_style
4243                     (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4244             } else if (strcmp(optarg, "mono") == 0) {
4245                 cairo_font_options_set_antialias
4246                     (im->font_options, CAIRO_ANTIALIAS_NONE);
4247                 cairo_font_options_set_hint_style
4248                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4249             } else {
4250                 rrd_set_error("unknown font-render-mode '%s'", optarg);
4251                 return;
4252             }
4253             break;
4254         case 'G':
4255             if (strcmp(optarg, "normal") == 0)
4256                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4257             else if (strcmp(optarg, "mono") == 0)
4258                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4259             else {
4260                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4261                 return;
4262             }
4263             break;
4264         case 'B':
4265             /* not supported curently */
4266             break;
4267         case 'W':
4268             strncpy(im->watermark, optarg, 100);
4269             im->watermark[99] = '\0';
4270             break;
4271         case '?':
4272             if (optopt != 0)
4273                 rrd_set_error("unknown option '%c'", optopt);
4274             else
4275                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4276             return;
4277         }
4278     }
4279
4280     if (im->logarithmic == 1 && im->minval <= 0) {
4281         rrd_set_error
4282             ("for a logarithmic yaxis you must specify a lower-limit > 0");
4283         return;
4284     }
4285
4286     if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4287         /* error string is set in parsetime.c */
4288         return;
4289     }
4290
4291     if (start_tmp < 3600 * 24 * 365 * 10) {
4292         rrd_set_error
4293             ("the first entry to fetch should be after 1980 (%ld)",
4294              start_tmp);
4295         return;
4296     }
4297
4298     if (end_tmp < start_tmp) {
4299         rrd_set_error
4300             ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4301         return;
4302     }
4303
4304     im->start = start_tmp;
4305     im->end = end_tmp;
4306     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4307 }
4308
4309 int rrd_graph_color(
4310     image_desc_t
4311     *im,
4312     char *var,
4313     char *err,
4314     int optional)
4315 {
4316     char     *color;
4317     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4318
4319     color = strstr(var, "#");
4320     if (color == NULL) {
4321         if (optional == 0) {
4322             rrd_set_error("Found no color in %s", err);
4323             return 0;
4324         }
4325         return 0;
4326     } else {
4327         int       n = 0;
4328         char     *rest;
4329         long unsigned int col;
4330
4331         rest = strstr(color, ":");
4332         if (rest != NULL)
4333             n = rest - color;
4334         else
4335             n = strlen(color);
4336         switch (n) {
4337         case 7:
4338             sscanf(color, "#%6lx%n", &col, &n);
4339             col = (col << 8) + 0xff /* shift left by 8 */ ;
4340             if (n != 7)
4341                 rrd_set_error("Color problem in %s", err);
4342             break;
4343         case 9:
4344             sscanf(color, "#%8lx%n", &col, &n);
4345             if (n == 9)
4346                 break;
4347         default:
4348             rrd_set_error("Color problem in %s", err);
4349         }
4350         if (rrd_test_error())
4351             return 0;
4352         gdp->col = gfx_hex_to_col(col);
4353         return n;
4354     }
4355 }
4356
4357
4358 int bad_format(
4359     char *fmt)
4360 {
4361     char     *ptr;
4362     int       n = 0;
4363
4364     ptr = fmt;
4365     while (*ptr != '\0')
4366         if (*ptr++ == '%') {
4367
4368             /* line cannot end with percent char */
4369             if (*ptr == '\0')
4370                 return 1;
4371             /* '%s', '%S' and '%%' are allowed */
4372             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4373                 ptr++;
4374             /* %c is allowed (but use only with vdef!) */
4375             else if (*ptr == 'c') {
4376                 ptr++;
4377                 n = 1;
4378             }
4379
4380             /* or else '% 6.2lf' and such are allowed */
4381             else {
4382                 /* optional padding character */
4383                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4384                     ptr++;
4385                 /* This should take care of 'm.n' with all three optional */
4386                 while (*ptr >= '0' && *ptr <= '9')
4387                     ptr++;
4388                 if (*ptr == '.')
4389                     ptr++;
4390                 while (*ptr >= '0' && *ptr <= '9')
4391                     ptr++;
4392                 /* Either 'le', 'lf' or 'lg' must follow here */
4393                 if (*ptr++ != 'l')
4394                     return 1;
4395                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4396                     ptr++;
4397                 else
4398                     return 1;
4399                 n++;
4400             }
4401         }
4402
4403     return (n != 1);
4404 }
4405
4406
4407 int vdef_parse(
4408     struct graph_desc_t
4409     *gdes,
4410     const char *const str)
4411 {
4412     /* A VDEF currently is either "func" or "param,func"
4413      * so the parsing is rather simple.  Change if needed.
4414      */
4415     double    param;
4416     char      func[30];
4417     int       n;
4418     char     *old_locale;
4419
4420     n = 0;
4421     old_locale = setlocale(LC_NUMERIC, "C");
4422     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4423     setlocale(LC_NUMERIC, old_locale);
4424     if (n == (int) strlen(str)) {   /* matched */
4425         ;
4426     } else {
4427         n = 0;
4428         sscanf(str, "%29[A-Z]%n", func, &n);
4429         if (n == (int) strlen(str)) {   /* matched */
4430             param = DNAN;
4431         } else {
4432             rrd_set_error
4433                 ("Unknown function string '%s' in VDEF '%s'",
4434                  str, gdes->vname);
4435             return -1;
4436         }
4437     }
4438     if (!strcmp("PERCENT", func))
4439         gdes->vf.op = VDEF_PERCENT;
4440     else if (!strcmp("MAXIMUM", func))
4441         gdes->vf.op = VDEF_MAXIMUM;
4442     else if (!strcmp("AVERAGE", func))
4443         gdes->vf.op = VDEF_AVERAGE;
4444     else if (!strcmp("STDEV", func))
4445         gdes->vf.op = VDEF_STDEV;
4446     else if (!strcmp("MINIMUM", func))
4447         gdes->vf.op = VDEF_MINIMUM;
4448     else if (!strcmp("TOTAL", func))
4449         gdes->vf.op = VDEF_TOTAL;
4450     else if (!strcmp("FIRST", func))
4451         gdes->vf.op = VDEF_FIRST;
4452     else if (!strcmp("LAST", func))
4453         gdes->vf.op = VDEF_LAST;
4454     else if (!strcmp("LSLSLOPE", func))
4455         gdes->vf.op = VDEF_LSLSLOPE;
4456     else if (!strcmp("LSLINT", func))
4457         gdes->vf.op = VDEF_LSLINT;
4458     else if (!strcmp("LSLCORREL", func))
4459         gdes->vf.op = VDEF_LSLCORREL;
4460     else {
4461         rrd_set_error
4462             ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4463         return -1;
4464     };
4465     switch (gdes->vf.op) {
4466     case VDEF_PERCENT:
4467         if (isnan(param)) { /* no parameter given */
4468             rrd_set_error
4469                 ("Function '%s' needs parameter in VDEF '%s'\n",
4470                  func, gdes->vname);
4471             return -1;
4472         };
4473         if (param >= 0.0 && param <= 100.0) {
4474             gdes->vf.param = param;
4475             gdes->vf.val = DNAN;    /* undefined */
4476             gdes->vf.when = 0;  /* undefined */
4477         } else {
4478             rrd_set_error
4479                 ("Parameter '%f' out of range in VDEF '%s'\n",
4480                  param, gdes->vname);
4481             return -1;
4482         };
4483         break;
4484     case VDEF_MAXIMUM:
4485     case VDEF_AVERAGE:
4486     case VDEF_STDEV:
4487     case VDEF_MINIMUM:
4488     case VDEF_TOTAL:
4489     case VDEF_FIRST:
4490     case VDEF_LAST:
4491     case VDEF_LSLSLOPE:
4492     case VDEF_LSLINT:
4493     case VDEF_LSLCORREL:
4494         if (isnan(param)) {
4495             gdes->vf.param = DNAN;
4496             gdes->vf.val = DNAN;
4497             gdes->vf.when = 0;
4498         } else {
4499             rrd_set_error
4500                 ("Function '%s' needs no parameter in VDEF '%s'\n",
4501                  func, gdes->vname);
4502             return -1;
4503         };
4504         break;
4505     };
4506     return 0;
4507 }
4508
4509
4510 int vdef_calc(
4511     image_desc_t *im,
4512     int gdi)
4513 {
4514     graph_desc_t *src, *dst;
4515     rrd_value_t *data;
4516     long      step, steps;
4517
4518     dst = &im->gdes[gdi];
4519     src = &im->gdes[dst->vidx];
4520     data = src->data + src->ds;
4521     steps = (src->end - src->start) / src->step;
4522 #if 0
4523     printf
4524         ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4525          src->start, src->end, steps);
4526 #endif
4527     switch (dst->vf.op) {
4528     case VDEF_PERCENT:{
4529         rrd_value_t *array;
4530         int       field;
4531         if ((array = malloc(steps * sizeof(double))) == NULL) {
4532             rrd_set_error("malloc VDEV_PERCENT");
4533             return -1;
4534         }
4535         for (step = 0; step < steps; step++) {
4536             array[step] = data[step * src->ds_cnt];
4537         }
4538         qsort(array, step, sizeof(double), vdef_percent_compar);
4539         field = (steps - 1) * dst->vf.param / 100;
4540         dst->vf.val = array[field];
4541         dst->vf.when = 0;   /* no time component */
4542         free(array);
4543 #if 0
4544         for (step = 0; step < steps; step++)
4545             printf("DEBUG: %3li:%10.2f %c\n",
4546                    step, array[step], step == field ? '*' : ' ');
4547 #endif
4548     }
4549         break;
4550     case VDEF_MAXIMUM:
4551         step = 0;
4552         while (step != steps && isnan(data[step * src->ds_cnt]))
4553             step++;
4554         if (step == steps) {
4555             dst->vf.val = DNAN;
4556             dst->vf.when = 0;
4557         } else {
4558             dst->vf.val = data[step * src->ds_cnt];
4559             dst->vf.when = src->start + (step + 1) * src->step;
4560         }
4561         while (step != steps) {
4562             if (finite(data[step * src->ds_cnt])) {
4563                 if (data[step * src->ds_cnt] > dst->vf.val) {
4564                     dst->vf.val = data[step * src->ds_cnt];
4565                     dst->vf.when = src->start + (step + 1) * src->step;
4566                 }
4567             }
4568             step++;
4569         }
4570         break;
4571     case VDEF_TOTAL:
4572     case VDEF_STDEV:
4573     case VDEF_AVERAGE:{
4574         int       cnt = 0;
4575         double    sum = 0.0;
4576         double    average = 0.0;
4577
4578         for (step = 0; step < steps; step++) {
4579             if (finite(data[step * src->ds_cnt])) {
4580                 sum += data[step * src->ds_cnt];
4581                 cnt++;
4582             };
4583         }
4584         if (cnt) {
4585             if (dst->vf.op == VDEF_TOTAL) {
4586                 dst->vf.val = sum * src->step;
4587                 dst->vf.when = 0;   /* no time component */
4588             } else if (dst->vf.op == VDEF_AVERAGE) {
4589                 dst->vf.val = sum / cnt;
4590                 dst->vf.when = 0;   /* no time component */
4591             } else {
4592                 average = sum / cnt;
4593                 sum = 0.0;
4594                 for (step = 0; step < steps; step++) {
4595                     if (finite(data[step * src->ds_cnt])) {
4596                         sum += pow((data[step * src->ds_cnt] - average), 2.0);
4597                     };
4598                 }
4599                 dst->vf.val = pow(sum / cnt, 0.5);
4600                 dst->vf.when = 0;   /* no time component */
4601             };
4602         } else {
4603             dst->vf.val = DNAN;
4604             dst->vf.when = 0;
4605         }
4606     }
4607         break;
4608     case VDEF_MINIMUM:
4609         step = 0;
4610         while (step != steps && isnan(data[step * src->ds_cnt]))
4611             step++;
4612         if (step == steps) {
4613             dst->vf.val = DNAN;
4614             dst->vf.when = 0;
4615         } else {
4616             dst->vf.val = data[step * src->ds_cnt];
4617             dst->vf.when = src->start + (step + 1) * src->step;
4618         }
4619         while (step != steps) {
4620             if (finite(data[step * src->ds_cnt])) {
4621                 if (data[step * src->ds_cnt] < dst->vf.val) {
4622                     dst->vf.val = data[step * src->ds_cnt];
4623                     dst->vf.when = src->start + (step + 1) * src->step;
4624                 }
4625             }
4626             step++;
4627         }
4628         break;
4629     case VDEF_FIRST:
4630         /* The time value returned here is one step before the
4631          * actual time value.  This is the start of the first
4632          * non-NaN interval.
4633          */
4634         step = 0;
4635         while (step != steps && isnan(data[step * src->ds_cnt]))
4636             step++;
4637         if (step == steps) {    /* all entries were NaN */
4638             dst->vf.val = DNAN;
4639             dst->vf.when = 0;
4640         } else {
4641             dst->vf.val = data[step * src->ds_cnt];
4642             dst->vf.when = src->start + step * src->step;
4643         }
4644         break;
4645     case VDEF_LAST:
4646         /* The time value returned here is the
4647          * actual time value.  This is the end of the last
4648          * non-NaN interval.
4649          */
4650         step = steps - 1;
4651         while (step >= 0 && isnan(data[step * src->ds_cnt]))
4652             step--;
4653         if (step < 0) { /* all entries were NaN */
4654             dst->vf.val = DNAN;
4655             dst->vf.when = 0;
4656         } else {
4657             dst->vf.val = data[step * src->ds_cnt];
4658             dst->vf.when = src->start + (step + 1) * src->step;
4659         }
4660         break;
4661     case VDEF_LSLSLOPE:
4662     case VDEF_LSLINT:
4663     case VDEF_LSLCORREL:{
4664         /* Bestfit line by linear least squares method */
4665
4666         int       cnt = 0;
4667         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4668
4669         SUMx = 0;
4670         SUMy = 0;
4671         SUMxy = 0;
4672         SUMxx = 0;
4673         SUMyy = 0;
4674         for (step = 0; step < steps; step++) {
4675             if (finite(data[step * src->ds_cnt])) {
4676                 cnt++;
4677                 SUMx += step;
4678                 SUMxx += step * step;
4679                 SUMxy += step * data[step * src->ds_cnt];
4680                 SUMy += data[step * src->ds_cnt];
4681                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4682             };
4683         }
4684
4685         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4686         y_intercept = (SUMy - slope * SUMx) / cnt;
4687         correl =
4688             (SUMxy -
4689              (SUMx * SUMy) / cnt) /
4690             sqrt((SUMxx -
4691                   (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4692         if (cnt) {
4693             if (dst->vf.op == VDEF_LSLSLOPE) {
4694                 dst->vf.val = slope;
4695                 dst->vf.when = 0;
4696             } else if (dst->vf.op == VDEF_LSLINT) {
4697                 dst->vf.val = y_intercept;
4698                 dst->vf.when = 0;
4699             } else if (dst->vf.op == VDEF_LSLCORREL) {
4700                 dst->vf.val = correl;
4701                 dst->vf.when = 0;
4702             };
4703         } else {
4704             dst->vf.val = DNAN;
4705             dst->vf.when = 0;
4706         }
4707     }
4708         break;
4709     }
4710     return 0;
4711 }
4712
4713 /* NaN < -INF < finite_values < INF */
4714 int vdef_percent_compar(
4715     const void
4716     *a,
4717     const void
4718     *b)
4719 {
4720     /* Equality is not returned; this doesn't hurt except
4721      * (maybe) for a little performance.
4722      */
4723
4724     /* First catch NaN values. They are smallest */
4725     if (isnan(*(double *) a))
4726         return -1;
4727     if (isnan(*(double *) b))
4728         return 1;
4729     /* NaN doesn't reach this part so INF and -INF are extremes.
4730      * The sign from isinf() is compatible with the sign we return
4731      */
4732     if (isinf(*(double *) a))
4733         return isinf(*(double *) a);
4734     if (isinf(*(double *) b))
4735         return isinf(*(double *) b);
4736     /* If we reach this, both values must be finite */
4737     if (*(double *) a < *(double *) b)
4738         return -1;
4739     else
4740         return 1;
4741 }
4742
4743 void grinfo_push(
4744     image_desc_t *im,
4745     char *key,
4746     enum info_type type,
4747     infoval value)
4748 {
4749     im->grinfo_current = info_push(im->grinfo_current, key, type, value);
4750     if (im->grinfo == NULL) {
4751         im->grinfo = im->grinfo_current;
4752     }
4753 }