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