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