549e08c783d097e006af7335faf56f9414a86ba5
[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
1653 /* place legends with color spots */
1654 int leg_place(
1655     image_desc_t *im,
1656     int calc_width)
1657 {
1658     /* graph labels */
1659     int       interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1660     int       border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1661     int       fill = 0, fill_last;
1662     double    legendwidth; // = im->ximg - 2 * border;
1663     int       leg_c = 0;
1664     double    leg_x = border;
1665     int       leg_y = 0; //im->yimg;
1666     int       leg_y_prev = 0; // im->yimg;
1667     int       leg_cc;
1668     double    glue = 0;
1669     int       i, ii, mark = 0;
1670     char      default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1671     int      *legspace;
1672     char     *tab;
1673     char      saved_legend[FMT_LEG_LEN + 5];
1674
1675     if(calc_width){
1676         legendwidth = 0;
1677     }
1678     else{
1679         legendwidth = im->legendwidth - 2 * border;
1680     }
1681
1682
1683     if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1684         if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1685             rrd_set_error("malloc for legspace");
1686             return -1;
1687         }
1688
1689         for (i = 0; i < im->gdes_c; i++) {
1690             char      prt_fctn; /*special printfunctions */
1691             if(calc_width){
1692                 strcpy(saved_legend, im->gdes[i].legend);
1693             }
1694
1695             fill_last = fill;
1696             /* hide legends for rules which are not displayed */
1697             if (im->gdes[i].gf == GF_TEXTALIGN) {
1698                 default_txtalign = im->gdes[i].txtalign;
1699             }
1700
1701             if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1702                 if (im->gdes[i].gf == GF_HRULE
1703                     && (im->gdes[i].yrule <
1704                         im->minval || im->gdes[i].yrule > im->maxval))
1705                     im->gdes[i].legend[0] = '\0';
1706                 if (im->gdes[i].gf == GF_VRULE
1707                     && (im->gdes[i].xrule <
1708                         im->start || im->gdes[i].xrule > im->end))
1709                     im->gdes[i].legend[0] = '\0';
1710             }
1711
1712             /* turn \\t into tab */
1713             while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1714                 memmove(tab, tab + 1, strlen(tab));
1715                 tab[0] = (char) 9;
1716             }
1717
1718             leg_cc = strlen(im->gdes[i].legend);
1719             /* is there a controle code at the end of the legend string ? */
1720             if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1721                 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1722                 leg_cc -= 2;
1723                 im->gdes[i].legend[leg_cc] = '\0';
1724             } else {
1725                 prt_fctn = '\0';
1726             }
1727             /* only valid control codes */
1728             if (prt_fctn != 'l' && prt_fctn != 'n' &&   /* a synonym for l */
1729                 prt_fctn != 'r' &&
1730                 prt_fctn != 'j' &&
1731                 prt_fctn != 'c' &&
1732                 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1733                 free(legspace);
1734                 rrd_set_error
1735                     ("Unknown control code at the end of '%s\\%c'",
1736                      im->gdes[i].legend, prt_fctn);
1737                 return -1;
1738             }
1739             /* \n -> \l */
1740             if (prt_fctn == 'n') {
1741                 prt_fctn = 'l';
1742             }
1743
1744             /* remove exess space from the end of the legend for \g */
1745             while (prt_fctn == 'g' &&
1746                    leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1747                 leg_cc--;
1748                 im->gdes[i].legend[leg_cc] = '\0';
1749             }
1750
1751             if (leg_cc != 0) {
1752
1753                 /* no interleg space if string ends in \g */
1754                 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1755                 if (fill > 0) {
1756                     fill += legspace[i];
1757                 }
1758                 fill +=
1759                     gfx_get_text_width(im,
1760                                        fill + border,
1761                                        im->
1762                                        text_prop
1763                                        [TEXT_PROP_LEGEND].
1764                                        font_desc,
1765                                        im->tabwidth, im->gdes[i].legend);
1766                 leg_c++;
1767             } else {
1768                 legspace[i] = 0;
1769             }
1770             /* who said there was a special tag ... ? */
1771             if (prt_fctn == 'g') {
1772                 prt_fctn = '\0';
1773             }
1774
1775             if (prt_fctn == '\0') {
1776                 if(calc_width && (fill > legendwidth)){
1777                     legendwidth = fill;
1778                 }
1779                 if (i == im->gdes_c - 1 || fill > legendwidth) {
1780                     /* just one legend item is left right or center */
1781                     switch (default_txtalign) {
1782                     case TXA_RIGHT:
1783                         prt_fctn = 'r';
1784                         break;
1785                     case TXA_CENTER:
1786                         prt_fctn = 'c';
1787                         break;
1788                     case TXA_JUSTIFIED:
1789                         prt_fctn = 'j';
1790                         break;
1791                     default:
1792                         prt_fctn = 'l';
1793                         break;
1794                     }
1795                 }
1796                 /* is it time to place the legends ? */
1797                 if (fill > legendwidth) {
1798                     if (leg_c > 1) {
1799                         /* go back one */
1800                         i--;
1801                         fill = fill_last;
1802                         leg_c--;
1803                     }
1804                 }
1805                 if (leg_c == 1 && prt_fctn == 'j') {
1806                     prt_fctn = 'l';
1807                 }
1808             }
1809
1810             if (prt_fctn != '\0') {
1811                 leg_x = border;
1812                 if (leg_c >= 2 && prt_fctn == 'j') {
1813                     glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1814                 } else {
1815                     glue = 0;
1816                 }
1817                 if (prt_fctn == 'c')
1818                     leg_x = (double)(legendwidth - fill) / 2.0;
1819                 if (prt_fctn == 'r')
1820                     leg_x = legendwidth - fill - border;
1821                 for (ii = mark; ii <= i; ii++) {
1822                     if (im->gdes[ii].legend[0] == '\0')
1823                         continue;   /* skip empty legends */
1824                     im->gdes[ii].leg_x = leg_x;
1825                     im->gdes[ii].leg_y = leg_y + border;
1826                     leg_x +=
1827                         (double)gfx_get_text_width(im, leg_x,
1828                                            im->
1829                                            text_prop
1830                                            [TEXT_PROP_LEGEND].
1831                                            font_desc,
1832                                            im->tabwidth, im->gdes[ii].legend)
1833                         +(double)legspace[ii]
1834                         + glue;
1835                 }
1836                 leg_y_prev = leg_y;
1837                 if (leg_x > border || prt_fctn == 's')
1838                     leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1839                 if (prt_fctn == 's')
1840                     leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1841
1842                 if(calc_width && (fill > legendwidth)){
1843                     legendwidth = fill;
1844                 }
1845                 fill = 0;
1846                 leg_c = 0;
1847                 mark = ii;
1848             }
1849
1850             if(calc_width){
1851                 strcpy(im->gdes[i].legend, saved_legend);
1852             }
1853         }
1854
1855         if(calc_width){
1856             im->legendwidth = legendwidth + 2 * border;
1857         }
1858         else{
1859             im->legendheight = leg_y + border * 0.6;
1860         }
1861         free(legspace);
1862     }
1863     return 0;
1864 }
1865
1866 /* create a grid on the graph. it determines what to do
1867    from the values of xsize, start and end */
1868
1869 /* the xaxis labels are determined from the number of seconds per pixel
1870    in the requested graph */
1871
1872 int calc_horizontal_grid(
1873     image_desc_t
1874     *im)
1875 {
1876     double    range;
1877     double    scaledrange;
1878     int       pixel, i;
1879     int       gridind = 0;
1880     int       decimals, fractionals;
1881
1882     im->ygrid_scale.labfact = 2;
1883     range = im->maxval - im->minval;
1884     scaledrange = range / im->magfact;
1885     /* does the scale of this graph make it impossible to put lines
1886        on it? If so, give up. */
1887     if (isnan(scaledrange)) {
1888         return 0;
1889     }
1890
1891     /* find grid spaceing */
1892     pixel = 1;
1893     if (isnan(im->ygridstep)) {
1894         if (im->extra_flags & ALTYGRID) {
1895             /* find the value with max number of digits. Get number of digits */
1896             decimals =
1897                 ceil(log10
1898                      (max(fabs(im->maxval), fabs(im->minval)) *
1899                       im->viewfactor / im->magfact));
1900             if (decimals <= 0)  /* everything is small. make place for zero */
1901                 decimals = 1;
1902             im->ygrid_scale.gridstep =
1903                 pow((double) 10,
1904                     floor(log10(range * im->viewfactor / im->magfact))) /
1905                 im->viewfactor * im->magfact;
1906             if (im->ygrid_scale.gridstep == 0)  /* range is one -> 0.1 is reasonable scale */
1907                 im->ygrid_scale.gridstep = 0.1;
1908             /* should have at least 5 lines but no more then 15 */
1909             if (range / im->ygrid_scale.gridstep < 5
1910                 && im->ygrid_scale.gridstep >= 30)
1911                 im->ygrid_scale.gridstep /= 10;
1912             if (range / im->ygrid_scale.gridstep > 15)
1913                 im->ygrid_scale.gridstep *= 10;
1914             if (range / im->ygrid_scale.gridstep > 5) {
1915                 im->ygrid_scale.labfact = 1;
1916                 if (range / im->ygrid_scale.gridstep > 8
1917                     || im->ygrid_scale.gridstep <
1918                     1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1919                     im->ygrid_scale.labfact = 2;
1920             } else {
1921                 im->ygrid_scale.gridstep /= 5;
1922                 im->ygrid_scale.labfact = 5;
1923             }
1924             fractionals =
1925                 floor(log10
1926                       (im->ygrid_scale.gridstep *
1927                        (double) im->ygrid_scale.labfact * im->viewfactor /
1928                        im->magfact));
1929             if (fractionals < 0) {  /* small amplitude. */
1930                 int       len = decimals - fractionals + 1;
1931
1932                 if (im->unitslength < len + 2)
1933                     im->unitslength = len + 2;
1934                 sprintf(im->ygrid_scale.labfmt,
1935                         "%%%d.%df%s", len,
1936                         -fractionals, (im->symbol != ' ' ? " %c" : ""));
1937             } else {
1938                 int       len = decimals + 1;
1939
1940                 if (im->unitslength < len + 2)
1941                     im->unitslength = len + 2;
1942                 sprintf(im->ygrid_scale.labfmt,
1943                         "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1944             }
1945         } else {        /* classic rrd grid */
1946             for (i = 0; ylab[i].grid > 0; i++) {
1947                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1948                 gridind = i;
1949                 if (pixel >= 5)
1950                     break;
1951             }
1952
1953             for (i = 0; i < 4; i++) {
1954                 if (pixel * ylab[gridind].lfac[i] >=
1955                     1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1956                     im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1957                     break;
1958                 }
1959             }
1960
1961             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1962         }
1963     } else {
1964         im->ygrid_scale.gridstep = im->ygridstep;
1965         im->ygrid_scale.labfact = im->ylabfact;
1966     }
1967     return 1;
1968 }
1969
1970 int draw_horizontal_grid(
1971     image_desc_t
1972     *im)
1973 {
1974     int       i;
1975     double    scaledstep;
1976     char      graph_label[100];
1977     int       nlabels = 0;
1978     double    X0 = im->xorigin;
1979     double    X1 = im->xorigin + im->xsize;
1980     int       sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1981     int       egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1982     double    MaxY;
1983     double second_axis_magfact = 0;
1984     char *second_axis_symb = "";
1985     
1986     scaledstep =
1987         im->ygrid_scale.gridstep /
1988         (double) im->magfact * (double) im->viewfactor;
1989     MaxY = scaledstep * (double) egrid;
1990     for (i = sgrid; i <= egrid; i++) {
1991         double    Y0 = ytr(im,
1992                            im->ygrid_scale.gridstep * i);
1993         double    YN = ytr(im,
1994                            im->ygrid_scale.gridstep * (i + 1));
1995
1996         if (floor(Y0 + 0.5) >=
1997             im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1998             /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1999                with the chosen settings. Add a label if required by settings, or if
2000                there is only one label so far and the next grid line is out of bounds. */
2001             if (i % im->ygrid_scale.labfact == 0
2002                 || (nlabels == 1
2003                     && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2004                 if (im->symbol == ' ') {
2005                     if (im->extra_flags & ALTYGRID) {
2006                         sprintf(graph_label,
2007                                 im->ygrid_scale.labfmt,
2008                                 scaledstep * (double) i);
2009                     } else {
2010                         if (MaxY < 10) {
2011                             sprintf(graph_label, "%4.1f",
2012                                     scaledstep * (double) i);
2013                         } else {
2014                             sprintf(graph_label, "%4.0f",
2015                                     scaledstep * (double) i);
2016                         }
2017                     }
2018                 } else {
2019                     char      sisym = (i == 0 ? ' ' : im->symbol);
2020
2021                     if (im->extra_flags & ALTYGRID) {
2022                         sprintf(graph_label,
2023                                 im->ygrid_scale.labfmt,
2024                                 scaledstep * (double) i, sisym);
2025                     } else {
2026                         if (MaxY < 10) {
2027                             sprintf(graph_label, "%4.1f %c",
2028                                     scaledstep * (double) i, sisym);
2029                         } else {
2030                             sprintf(graph_label, "%4.0f %c",
2031                                     scaledstep * (double) i, sisym);
2032                         }
2033                     }
2034                 }
2035                 nlabels++;
2036                 if (im->second_axis_scale != 0){
2037                         char graph_label_right[100];
2038                         double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2039                         if (im->second_axis_format[0] == '\0'){
2040                             if (!second_axis_magfact){
2041                                 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2042                                 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2043                             }
2044                             sval /= second_axis_magfact;
2045  
2046                             if(MaxY < 10) { 
2047                                 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2048                             } else {
2049                                 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2050                             }
2051                         }
2052                         else {
2053                            sprintf(graph_label_right,im->second_axis_format,sval);
2054                         }        
2055                         gfx_text ( im,
2056                                X1+7, Y0,
2057                                im->graph_col[GRC_FONT],
2058                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2059                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2060                                graph_label_right );
2061                 }
2062  
2063                 gfx_text(im,
2064                          X0 -
2065                          im->
2066                          text_prop[TEXT_PROP_AXIS].
2067                          size, Y0,
2068                          im->graph_col[GRC_FONT],
2069                          im->
2070                          text_prop[TEXT_PROP_AXIS].
2071                          font_desc,
2072                          im->tabwidth, 0.0,
2073                          GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2074                 gfx_line(im, X0 - 2, Y0, X0, Y0,
2075                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2076                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2077                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2078                 gfx_dashed_line(im, X0 - 2, Y0,
2079                                 X1 + 2, Y0,
2080                                 MGRIDWIDTH,
2081                                 im->
2082                                 graph_col
2083                                 [GRC_MGRID],
2084                                 im->grid_dash_on, im->grid_dash_off);
2085             } else if (!(im->extra_flags & NOMINOR)) {
2086                 gfx_line(im,
2087                          X0 - 2, Y0,
2088                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2089                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2090                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2091                 gfx_dashed_line(im, X0 - 1, Y0,
2092                                 X1 + 1, Y0,
2093                                 GRIDWIDTH,
2094                                 im->
2095                                 graph_col[GRC_GRID],
2096                                 im->grid_dash_on, im->grid_dash_off);
2097             }
2098         }
2099     }
2100     return 1;
2101 }
2102
2103 /* this is frexp for base 10 */
2104 double    frexp10(
2105     double,
2106     double *);
2107 double frexp10(
2108     double x,
2109     double *e)
2110 {
2111     double    mnt;
2112     int       iexp;
2113
2114     iexp = floor(log((double)fabs(x)) / log((double)10));
2115     mnt = x / pow(10.0, iexp);
2116     if (mnt >= 10.0) {
2117         iexp++;
2118         mnt = x / pow(10.0, iexp);
2119     }
2120     *e = iexp;
2121     return mnt;
2122 }
2123
2124
2125 /* logaritmic horizontal grid */
2126 int horizontal_log_grid(
2127     image_desc_t
2128     *im)
2129 {
2130     double    yloglab[][10] = {
2131         {
2132          1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2133          0.0, 0.0, 0.0}, {
2134                           1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2135                           0.0, 0.0, 0.0}, {
2136                                            1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2137                                            0.0, 0.0, 0.0}, {
2138                                                             1.0, 2.0, 4.0,
2139                                                             6.0, 8.0, 10.,
2140                                                             0.0,
2141                                                             0.0, 0.0, 0.0}, {
2142                                                                              1.0,
2143                                                                              2.0,
2144                                                                              3.0,
2145                                                                              4.0,
2146                                                                              5.0,
2147                                                                              6.0,
2148                                                                              7.0,
2149                                                                              8.0,
2150                                                                              9.0,
2151                                                                              10.},
2152         {
2153          0, 0, 0, 0, 0, 0, 0, 0, 0, 0}  /* last line */
2154     };
2155     int       i, j, val_exp, min_exp;
2156     double    nex;      /* number of decades in data */
2157     double    logscale; /* scale in logarithmic space */
2158     int       exfrac = 1;   /* decade spacing */
2159     int       mid = -1; /* row in yloglab for major grid */
2160     double    mspac;    /* smallest major grid spacing (pixels) */
2161     int       flab;     /* first value in yloglab to use */
2162     double    value, tmp, pre_value;
2163     double    X0, X1, Y0;
2164     char      graph_label[100];
2165
2166     nex = log10(im->maxval / im->minval);
2167     logscale = im->ysize / nex;
2168     /* major spacing for data with high dynamic range */
2169     while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2170         if (exfrac == 1)
2171             exfrac = 3;
2172         else
2173             exfrac += 3;
2174     }
2175
2176     /* major spacing for less dynamic data */
2177     do {
2178         /* search best row in yloglab */
2179         mid++;
2180         for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2181         mspac = logscale * log10(10.0 / yloglab[mid][i]);
2182     }
2183     while (mspac >
2184            2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2185     if (mid)
2186         mid--;
2187     /* find first value in yloglab */
2188     for (flab = 0;
2189          yloglab[mid][flab] < 10
2190          && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2191     if (yloglab[mid][flab] == 10.0) {
2192         tmp += 1.0;
2193         flab = 0;
2194     }
2195     val_exp = tmp;
2196     if (val_exp % exfrac)
2197         val_exp += abs(-val_exp % exfrac);
2198     X0 = im->xorigin;
2199     X1 = im->xorigin + im->xsize;
2200     /* draw grid */
2201     pre_value = DNAN;
2202     while (1) {
2203
2204         value = yloglab[mid][flab] * pow(10.0, val_exp);
2205         if (AlmostEqual2sComplement(value, pre_value, 4))
2206             break;      /* it seems we are not converging */
2207         pre_value = value;
2208         Y0 = ytr(im, value);
2209         if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2210             break;
2211         /* major grid line */
2212         gfx_line(im,
2213                  X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2214         gfx_line(im, X1, Y0, X1 + 2, Y0,
2215                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2216         gfx_dashed_line(im, X0 - 2, Y0,
2217                         X1 + 2, Y0,
2218                         MGRIDWIDTH,
2219                         im->
2220                         graph_col
2221                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2222         /* label */
2223         if (im->extra_flags & FORCE_UNITS_SI) {
2224             int       scale;
2225             double    pvalue;
2226             char      symbol;
2227
2228             scale = floor(val_exp / 3.0);
2229             if (value >= 1.0)
2230                 pvalue = pow(10.0, val_exp % 3);
2231             else
2232                 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2233             pvalue *= yloglab[mid][flab];
2234             if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2235                 && ((scale + si_symbcenter) >= 0))
2236                 symbol = si_symbol[scale + si_symbcenter];
2237             else
2238                 symbol = '?';
2239             sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2240         } else {            
2241             sprintf(graph_label, "%3.0e", value);
2242         }
2243         if (im->second_axis_scale != 0){
2244                 char graph_label_right[100];
2245                 double sval = value*im->second_axis_scale+im->second_axis_shift;
2246                 if (im->second_axis_format[0] == '\0'){
2247                         if (im->extra_flags & FORCE_UNITS_SI) {
2248                                 double mfac = 1;
2249                                 char   *symb = "";
2250                                 auto_scale(im,&sval,&symb,&mfac);
2251                                 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2252                         }
2253                         else {        
2254                                 sprintf(graph_label_right,"%3.0e", sval);
2255                         }
2256                 }
2257                 else {
2258                       sprintf(graph_label_right,im->second_axis_format,sval);
2259                 }    
2260     
2261                 gfx_text ( im,
2262                                X1+7, Y0,
2263                                im->graph_col[GRC_FONT],
2264                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2265                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2266                                graph_label_right );
2267         }
2268       
2269         gfx_text(im,
2270                  X0 -
2271                  im->
2272                  text_prop[TEXT_PROP_AXIS].
2273                  size, Y0,
2274                  im->graph_col[GRC_FONT],
2275                  im->
2276                  text_prop[TEXT_PROP_AXIS].
2277                  font_desc,
2278                  im->tabwidth, 0.0,
2279                  GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2280         /* minor grid */
2281         if (mid < 4 && exfrac == 1) {
2282             /* find first and last minor line behind current major line
2283              * i is the first line and j tha last */
2284             if (flab == 0) {
2285                 min_exp = val_exp - 1;
2286                 for (i = 1; yloglab[mid][i] < 10.0; i++);
2287                 i = yloglab[mid][i - 1] + 1;
2288                 j = 10;
2289             } else {
2290                 min_exp = val_exp;
2291                 i = yloglab[mid][flab - 1] + 1;
2292                 j = yloglab[mid][flab];
2293             }
2294
2295             /* draw minor lines below current major line */
2296             for (; i < j; i++) {
2297
2298                 value = i * pow(10.0, min_exp);
2299                 if (value < im->minval)
2300                     continue;
2301                 Y0 = ytr(im, value);
2302                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2303                     break;
2304                 /* draw lines */
2305                 gfx_line(im,
2306                          X0 - 2, Y0,
2307                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2308                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2309                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2310                 gfx_dashed_line(im, X0 - 1, Y0,
2311                                 X1 + 1, Y0,
2312                                 GRIDWIDTH,
2313                                 im->
2314                                 graph_col[GRC_GRID],
2315                                 im->grid_dash_on, im->grid_dash_off);
2316             }
2317         } else if (exfrac > 1) {
2318             for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2319                 value = pow(10.0, i);
2320                 if (value < im->minval)
2321                     continue;
2322                 Y0 = ytr(im, value);
2323                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2324                     break;
2325                 /* draw lines */
2326                 gfx_line(im,
2327                          X0 - 2, Y0,
2328                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2329                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2330                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2331                 gfx_dashed_line(im, X0 - 1, Y0,
2332                                 X1 + 1, Y0,
2333                                 GRIDWIDTH,
2334                                 im->
2335                                 graph_col[GRC_GRID],
2336                                 im->grid_dash_on, im->grid_dash_off);
2337             }
2338         }
2339
2340         /* next decade */
2341         if (yloglab[mid][++flab] == 10.0) {
2342             flab = 0;
2343             val_exp += exfrac;
2344         }
2345     }
2346
2347     /* draw minor lines after highest major line */
2348     if (mid < 4 && exfrac == 1) {
2349         /* find first and last minor line below current major line
2350          * i is the first line and j tha last */
2351         if (flab == 0) {
2352             min_exp = val_exp - 1;
2353             for (i = 1; yloglab[mid][i] < 10.0; i++);
2354             i = yloglab[mid][i - 1] + 1;
2355             j = 10;
2356         } else {
2357             min_exp = val_exp;
2358             i = yloglab[mid][flab - 1] + 1;
2359             j = yloglab[mid][flab];
2360         }
2361
2362         /* draw minor lines below current major line */
2363         for (; i < j; i++) {
2364
2365             value = i * pow(10.0, min_exp);
2366             if (value < im->minval)
2367                 continue;
2368             Y0 = ytr(im, value);
2369             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2370                 break;
2371             /* draw lines */
2372             gfx_line(im,
2373                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2374             gfx_line(im, X1, Y0, X1 + 2, Y0,
2375                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2376             gfx_dashed_line(im, X0 - 1, Y0,
2377                             X1 + 1, Y0,
2378                             GRIDWIDTH,
2379                             im->
2380                             graph_col[GRC_GRID],
2381                             im->grid_dash_on, im->grid_dash_off);
2382         }
2383     }
2384     /* fancy minor gridlines */
2385     else if (exfrac > 1) {
2386         for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2387             value = pow(10.0, i);
2388             if (value < im->minval)
2389                 continue;
2390             Y0 = ytr(im, value);
2391             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2392                 break;
2393             /* draw lines */
2394             gfx_line(im,
2395                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2396             gfx_line(im, X1, Y0, X1 + 2, Y0,
2397                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2398             gfx_dashed_line(im, X0 - 1, Y0,
2399                             X1 + 1, Y0,
2400                             GRIDWIDTH,
2401                             im->
2402                             graph_col[GRC_GRID],
2403                             im->grid_dash_on, im->grid_dash_off);
2404         }
2405     }
2406
2407     return 1;
2408 }
2409
2410
2411 void vertical_grid(
2412     image_desc_t *im)
2413 {
2414     int       xlab_sel; /* which sort of label and grid ? */
2415     time_t    ti, tilab, timajor;
2416     long      factor;
2417     char      graph_label[100];
2418     double    X0, Y0, Y1;   /* points for filled graph and more */
2419     struct tm tm;
2420
2421     /* the type of time grid is determined by finding
2422        the number of seconds per pixel in the graph */
2423     if (im->xlab_user.minsec == -1) {
2424         factor = (im->end - im->start) / im->xsize;
2425         xlab_sel = 0;
2426         while (xlab[xlab_sel + 1].minsec !=
2427                -1 && xlab[xlab_sel + 1].minsec <= factor) {
2428             xlab_sel++;
2429         }               /* pick the last one */
2430         while (xlab[xlab_sel - 1].minsec ==
2431                xlab[xlab_sel].minsec
2432                && xlab[xlab_sel].length > (im->end - im->start)) {
2433             xlab_sel--;
2434         }               /* go back to the smallest size */
2435         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2436         im->xlab_user.gridst = xlab[xlab_sel].gridst;
2437         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2438         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2439         im->xlab_user.labtm = xlab[xlab_sel].labtm;
2440         im->xlab_user.labst = xlab[xlab_sel].labst;
2441         im->xlab_user.precis = xlab[xlab_sel].precis;
2442         im->xlab_user.stst = xlab[xlab_sel].stst;
2443     }
2444
2445     /* y coords are the same for every line ... */
2446     Y0 = im->yorigin;
2447     Y1 = im->yorigin - im->ysize;
2448     /* paint the minor grid */
2449     if (!(im->extra_flags & NOMINOR)) {
2450         for (ti = find_first_time(im->start,
2451                                   im->
2452                                   xlab_user.
2453                                   gridtm,
2454                                   im->
2455                                   xlab_user.
2456                                   gridst),
2457              timajor =
2458              find_first_time(im->start,
2459                              im->xlab_user.
2460                              mgridtm,
2461                              im->xlab_user.
2462                              mgridst);
2463              ti < im->end;
2464              ti =
2465              find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2466             ) {
2467             /* are we inside the graph ? */
2468             if (ti < im->start || ti > im->end)
2469                 continue;
2470             while (timajor < ti) {
2471                 timajor = find_next_time(timajor,
2472                                          im->
2473                                          xlab_user.
2474                                          mgridtm, im->xlab_user.mgridst);
2475             }
2476             if (ti == timajor)
2477                 continue;   /* skip as falls on major grid line */
2478             X0 = xtr(im, ti);
2479             gfx_line(im, X0, Y1 - 2, X0, Y1,
2480                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2481             gfx_line(im, X0, Y0, X0, Y0 + 2,
2482                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2483             gfx_dashed_line(im, X0, Y0 + 1, X0,
2484                             Y1 - 1, GRIDWIDTH,
2485                             im->
2486                             graph_col[GRC_GRID],
2487                             im->grid_dash_on, im->grid_dash_off);
2488         }
2489     }
2490
2491     /* paint the major grid */
2492     for (ti = find_first_time(im->start,
2493                               im->
2494                               xlab_user.
2495                               mgridtm,
2496                               im->
2497                               xlab_user.
2498                               mgridst);
2499          ti < im->end;
2500          ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2501         ) {
2502         /* are we inside the graph ? */
2503         if (ti < im->start || ti > im->end)
2504             continue;
2505         X0 = xtr(im, ti);
2506         gfx_line(im, X0, Y1 - 2, X0, Y1,
2507                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2508         gfx_line(im, X0, Y0, X0, Y0 + 3,
2509                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2510         gfx_dashed_line(im, X0, Y0 + 3, X0,
2511                         Y1 - 2, MGRIDWIDTH,
2512                         im->
2513                         graph_col
2514                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2515     }
2516     /* paint the labels below the graph */
2517     for (ti =
2518          find_first_time(im->start -
2519                          im->xlab_user.
2520                          precis / 2,
2521                          im->xlab_user.
2522                          labtm,
2523                          im->xlab_user.
2524                          labst);
2525          ti <=
2526          im->end -
2527          im->xlab_user.precis / 2;
2528          ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2529         ) {
2530         tilab = ti + im->xlab_user.precis / 2;  /* correct time for the label */
2531         /* are we inside the graph ? */
2532         if (tilab < im->start || tilab > im->end)
2533             continue;
2534 #if HAVE_STRFTIME
2535         localtime_r(&tilab, &tm);
2536         strftime(graph_label, 99, im->xlab_user.stst, &tm);
2537 #else
2538 # error "your libc has no strftime I guess we'll abort the exercise here."
2539 #endif
2540         gfx_text(im,
2541                  xtr(im, tilab),
2542                  Y0 + 3,
2543                  im->graph_col[GRC_FONT],
2544                  im->
2545                  text_prop[TEXT_PROP_AXIS].
2546                  font_desc,
2547                  im->tabwidth, 0.0,
2548                  GFX_H_CENTER, GFX_V_TOP, graph_label);
2549     }
2550
2551 }
2552
2553
2554 void axis_paint(
2555     image_desc_t *im)
2556 {
2557     /* draw x and y axis */
2558     /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2559        im->xorigin+im->xsize,im->yorigin-im->ysize,
2560        GRIDWIDTH, im->graph_col[GRC_AXIS]);
2561
2562        gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2563        im->xorigin+im->xsize,im->yorigin-im->ysize,
2564        GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2565
2566     gfx_line(im, im->xorigin - 4,
2567              im->yorigin,
2568              im->xorigin + im->xsize +
2569              4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2570     gfx_line(im, im->xorigin,
2571              im->yorigin + 4,
2572              im->xorigin,
2573              im->yorigin - im->ysize -
2574              4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2575     /* arrow for X and Y axis direction */
2576     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 */
2577                  im->graph_col[GRC_ARROW]);
2578     gfx_close_path(im);
2579     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 */
2580                  im->graph_col[GRC_ARROW]);
2581     gfx_close_path(im);
2582     if (im->second_axis_scale != 0){
2583        gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2584                          im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2585                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2586        gfx_new_area ( im, 
2587                    im->xorigin+im->xsize-2,  im->yorigin-im->ysize-2,
2588                    im->xorigin+im->xsize+3,  im->yorigin-im->ysize-2,
2589                    im->xorigin+im->xsize,    im->yorigin-im->ysize-7, /* LINEOFFSET */
2590                    im->graph_col[GRC_ARROW]);
2591        gfx_close_path(im);
2592     }
2593
2594 }
2595
2596 void grid_paint(
2597     image_desc_t *im)
2598 {
2599     long      i;
2600     int       res = 0;
2601     double    X0, Y0;   /* points for filled graph and more */
2602     struct gfx_color_t water_color;
2603
2604     /* draw 3d border */
2605     gfx_new_area(im, 0, im->yimg,
2606                  2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2607     gfx_add_point(im, im->ximg - 2, 2);
2608     gfx_add_point(im, im->ximg, 0);
2609     gfx_add_point(im, 0, 0);
2610     gfx_close_path(im);
2611     gfx_new_area(im, 2, im->yimg - 2,
2612                  im->ximg - 2,
2613                  im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2614     gfx_add_point(im, im->ximg, 0);
2615     gfx_add_point(im, im->ximg, im->yimg);
2616     gfx_add_point(im, 0, im->yimg);
2617     gfx_close_path(im);
2618     if (im->draw_x_grid == 1)
2619         vertical_grid(im);
2620     if (im->draw_y_grid == 1) {
2621         if (im->logarithmic) {
2622             res = horizontal_log_grid(im);
2623         } else {
2624             res = draw_horizontal_grid(im);
2625         }
2626
2627         /* dont draw horizontal grid if there is no min and max val */
2628         if (!res) {
2629             char     *nodata = "No Data found";
2630
2631             gfx_text(im, im->ximg / 2,
2632                      (2 * im->yorigin -
2633                       im->ysize) / 2,
2634                      im->graph_col[GRC_FONT],
2635                      im->
2636                      text_prop[TEXT_PROP_AXIS].
2637                      font_desc,
2638                      im->tabwidth, 0.0,
2639                      GFX_H_CENTER, GFX_V_CENTER, nodata);
2640         }
2641     }
2642
2643     /* yaxis unit description */
2644     if (im->ylegend[0] != '\0'){
2645         gfx_text(im,
2646                  im->xOriginLegendY+10,
2647                  im->yOriginLegendY,
2648                  im->graph_col[GRC_FONT],
2649                  im->
2650                  text_prop[TEXT_PROP_UNIT].
2651                  font_desc,
2652                  im->tabwidth,
2653                  RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2654
2655     }
2656     if (im->second_axis_legend[0] != '\0'){
2657             gfx_text( im,
2658                   im->xOriginLegendY2+10, 
2659                   im->yOriginLegendY2,
2660                   im->graph_col[GRC_FONT],
2661                   im->text_prop[TEXT_PROP_UNIT].font_desc,
2662                   im->tabwidth, 
2663                   RRDGRAPH_YLEGEND_ANGLE,
2664                   GFX_H_CENTER, GFX_V_CENTER,
2665                   im->second_axis_legend);
2666     }        
2667  
2668     /* graph title */
2669     gfx_text(im,
2670              im->xOriginTitle, im->yOriginTitle+6,
2671              im->graph_col[GRC_FONT],
2672              im->
2673              text_prop[TEXT_PROP_TITLE].
2674              font_desc,
2675              im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2676     /* rrdtool 'logo' */
2677     if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2678         water_color = im->graph_col[GRC_FONT];
2679         water_color.alpha = 0.3;
2680         double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2681         gfx_text(im, xpos, 5,
2682                  water_color,
2683                  im->
2684                  text_prop[TEXT_PROP_WATERMARK].
2685                  font_desc, im->tabwidth,
2686                  -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2687     }    
2688     /* graph watermark */
2689     if (im->watermark[0] != '\0') {
2690         water_color = im->graph_col[GRC_FONT];
2691         water_color.alpha = 0.3;
2692         gfx_text(im,
2693                  im->ximg / 2, im->yimg - 6,
2694                  water_color,
2695                  im->
2696                  text_prop[TEXT_PROP_WATERMARK].
2697                  font_desc, im->tabwidth, 0,
2698                  GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2699     }
2700
2701     /* graph labels */
2702     if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2703         for (i = 0; i < im->gdes_c; i++) {
2704             if (im->gdes[i].legend[0] == '\0')
2705                 continue;
2706             /* im->gdes[i].leg_y is the bottom of the legend */
2707             X0 = im->xOriginLegend + im->gdes[i].leg_x;
2708             Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2709             gfx_text(im, X0, Y0,
2710                      im->graph_col[GRC_FONT],
2711                      im->
2712                      text_prop
2713                      [TEXT_PROP_LEGEND].font_desc,
2714                      im->tabwidth, 0.0,
2715                      GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2716             /* The legend for GRAPH items starts with "M " to have
2717                enough space for the box */
2718             if (im->gdes[i].gf != GF_PRINT &&
2719                 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2720                 double    boxH, boxV;
2721                 double    X1, Y1;
2722
2723                 boxH = gfx_get_text_width(im, 0,
2724                                           im->
2725                                           text_prop
2726                                           [TEXT_PROP_LEGEND].
2727                                           font_desc,
2728                                           im->tabwidth, "o") * 1.2;
2729                 boxV = boxH;
2730                 /* shift the box up a bit */
2731                 Y0 -= boxV * 0.4;
2732                 /* make sure transparent colors show up the same way as in the graph */
2733                 gfx_new_area(im,
2734                              X0, Y0 - boxV,
2735                              X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2736                 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2737                 gfx_close_path(im);
2738                 gfx_new_area(im, X0, Y0 - boxV, X0,
2739                              Y0, X0 + boxH, Y0, im->gdes[i].col);
2740                 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2741                 gfx_close_path(im);
2742                 cairo_save(im->cr);
2743                 cairo_new_path(im->cr);
2744                 cairo_set_line_width(im->cr, 1.0);
2745                 X1 = X0 + boxH;
2746                 Y1 = Y0 - boxV;
2747                 gfx_line_fit(im, &X0, &Y0);
2748                 gfx_line_fit(im, &X1, &Y1);
2749                 cairo_move_to(im->cr, X0, Y0);
2750                 cairo_line_to(im->cr, X1, Y0);
2751                 cairo_line_to(im->cr, X1, Y1);
2752                 cairo_line_to(im->cr, X0, Y1);
2753                 cairo_close_path(im->cr);
2754                 cairo_set_source_rgba(im->cr,
2755                                       im->
2756                                       graph_col
2757                                       [GRC_FRAME].
2758                                       red,
2759                                       im->
2760                                       graph_col
2761                                       [GRC_FRAME].
2762                                       green,
2763                                       im->
2764                                       graph_col
2765                                       [GRC_FRAME].
2766                                       blue, im->graph_col[GRC_FRAME].alpha);
2767                 if (im->gdes[i].dash) {
2768                     /* make box borders in legend dashed if the graph is dashed */
2769                     double    dashes[] = {
2770                         3.0
2771                     };
2772                     cairo_set_dash(im->cr, dashes, 1, 0.0);
2773                 }
2774                 cairo_stroke(im->cr);
2775                 cairo_restore(im->cr);
2776             }
2777         }
2778     }
2779 }
2780
2781
2782 /*****************************************************
2783  * lazy check make sure we rely need to create this graph
2784  *****************************************************/
2785
2786 int lazy_check(
2787     image_desc_t *im)
2788 {
2789     FILE     *fd = NULL;
2790     int       size = 1;
2791     struct stat imgstat;
2792
2793     if (im->lazy == 0)
2794         return 0;       /* no lazy option */
2795     if (strlen(im->graphfile) == 0)
2796         return 0;       /* inmemory option */
2797     if (stat(im->graphfile, &imgstat) != 0)
2798         return 0;       /* can't stat */
2799     /* one pixel in the existing graph is more then what we would
2800        change here ... */
2801     if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2802         return 0;
2803     if ((fd = fopen(im->graphfile, "rb")) == NULL)
2804         return 0;       /* the file does not exist */
2805     switch (im->imgformat) {
2806     case IF_PNG:
2807         size = PngSize(fd, &(im->ximg), &(im->yimg));
2808         break;
2809     default:
2810         size = 1;
2811     }
2812     fclose(fd);
2813     return size;
2814 }
2815
2816
2817 int graph_size_location(
2818     image_desc_t
2819     *im,
2820     int elements)
2821 {
2822     /* The actual size of the image to draw is determined from
2823      ** several sources.  The size given on the command line is
2824      ** the graph area but we need more as we have to draw labels
2825      ** and other things outside the graph area. If the option 
2826      ** --full-size-mode is selected the size defines the total 
2827      ** image size and the size available for the graph is 
2828      ** calculated.
2829      */
2830
2831     /** +---+-----------------------------------+
2832      ** | y |...............graph title.........|
2833      ** |   +---+-------------------------------+
2834      ** | a | y |                               |
2835      ** | x |   |                               |
2836      ** | i | a |                               |    
2837      ** | s | x |       main graph area         |
2838      ** |   | i |                               |
2839      ** | t | s |                               |
2840      ** | i |   |                               |
2841      ** | t | l |                               |
2842      ** | l | b +-------------------------------+
2843      ** | e | l |       x axis labels           |
2844      ** +---+---+-------------------------------+
2845      ** |....................legends............|
2846      ** +---------------------------------------+
2847      ** |                   watermark           |
2848      ** +---------------------------------------+
2849      */
2850
2851     int       Xvertical = 0, Xvertical2 = 0, Ytitle =
2852         0, Xylabel = 0, Xmain = 0, Ymain =
2853         0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2854
2855     // no legends and no the shall be plotted it's easy
2856     if (im->extra_flags & ONLY_GRAPH) {
2857         im->xorigin = 0;
2858         im->ximg = im->xsize;
2859         im->yimg = im->ysize;
2860         im->yorigin = im->ysize;
2861         ytr(im, DNAN);
2862         return 0;
2863     }
2864
2865     if(im->watermark[0] != '\0') {
2866         Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2867     }
2868
2869     // calculate the width of the left vertical legend
2870     if (im->ylegend[0] != '\0') {
2871         Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2872     }
2873
2874     // calculate the width of the right vertical legend
2875     if (im->second_axis_legend[0] != '\0') {
2876         Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2877     }
2878     else{
2879         Xvertical2 = Xspacing;
2880     }
2881
2882     if (im->title[0] != '\0') {
2883         /* The title is placed "inbetween" two text lines so it
2884          ** automatically has some vertical spacing.  The horizontal
2885          ** spacing is added here, on each side.
2886          */
2887         /* if necessary, reduce the font size of the title until it fits the image width */
2888         Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2889     }
2890     else{
2891         // we have no title; get a little clearing from the top
2892         Ytitle = 1.5 * Yspacing;
2893     }
2894
2895     if (elements) {
2896         if (im->draw_x_grid) {
2897             // calculate the height of the horizontal labelling
2898             Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2899         }
2900         if (im->draw_y_grid || im->forceleftspace) {
2901             // calculate the width of the vertical labelling
2902             Xylabel =
2903                 gfx_get_text_width(im, 0,
2904                                    im->text_prop[TEXT_PROP_AXIS].font_desc,
2905                                    im->tabwidth, "0") * im->unitslength;
2906         }
2907     }
2908
2909     // add some space to the labelling
2910     Xylabel += Xspacing;
2911
2912     /* If the legend is printed besides the graph the width has to be
2913      ** calculated first. Placing the legend north or south of the 
2914      ** graph requires the width calculation first, so the legend is 
2915      ** skipped for the moment.
2916      */
2917     im->legendheight = 0;
2918     im->legendwidth = 0;
2919     if (!(im->extra_flags & NOLEGEND)) {
2920         if(im->legendposition == WEST || im->legendposition == EAST){
2921             if (leg_place(im, 1) == -1){
2922                 return -1;
2923             }
2924         }
2925     }
2926
2927     if (im->extra_flags & FULL_SIZE_MODE) {
2928
2929         /* The actual size of the image to draw has been determined by the user.
2930          ** The graph area is the space remaining after accounting for the legend,
2931          ** the watermark, the axis labels, and the title.
2932          */
2933         im->ximg = im->xsize;
2934         im->yimg = im->ysize;
2935         Xmain = im->ximg;
2936         Ymain = im->yimg;
2937
2938         /* Now calculate the total size.  Insert some spacing where
2939            desired.  im->xorigin and im->yorigin need to correspond
2940            with the lower left corner of the main graph area or, if
2941            this one is not set, the imaginary box surrounding the
2942            pie chart area. */
2943         /* Initial size calculation for the main graph area */
2944
2945         Xmain -= Xylabel;// + Xspacing;
2946         if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
2947             Xmain -= im->legendwidth;// + Xspacing;
2948         }
2949         if (im->second_axis_scale != 0){
2950             Xmain -= Xylabel;
2951         }
2952         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2953             Xmain -= Xspacing;
2954         }
2955
2956         Xmain -= Xvertical + Xvertical2;
2957
2958         /* limit the remaining space to 0 */
2959         if(Xmain < 1){
2960             Xmain = 1;
2961         }
2962         im->xsize = Xmain;
2963
2964         /* Putting the legend north or south, the height can now be calculated */
2965         if (!(im->extra_flags & NOLEGEND)) {
2966             if(im->legendposition == NORTH || im->legendposition == SOUTH){
2967                 im->legendwidth = im->ximg;
2968                 if (leg_place(im, 0) == -1){
2969                     return -1;
2970                 }
2971             }
2972         }
2973
2974         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
2975             Ymain -=  Yxlabel + im->legendheight;
2976         }
2977         else{
2978             Ymain -= Yxlabel;
2979         }
2980         
2981         /* reserve space for the title *or* some padding above the graph */
2982         Ymain -= Ytitle;
2983
2984             /* reserve space for padding below the graph */
2985         if (im->extra_flags & NOLEGEND) {
2986             Ymain -= Yspacing;
2987         }
2988
2989         if (im->watermark[0] != '\0') {
2990             Ymain -= Ywatermark;
2991         }
2992         /* limit the remaining height to 0 */
2993         if(Ymain < 1){
2994             Ymain = 1;
2995         }
2996         im->ysize = Ymain;
2997     } else {            /* dimension options -width and -height refer to the dimensions of the main graph area */
2998
2999         /* The actual size of the image to draw is determined from
3000          ** several sources.  The size given on the command line is
3001          ** the graph area but we need more as we have to draw labels
3002          ** and other things outside the graph area.
3003          */
3004
3005         if (elements) {
3006             Xmain = im->xsize; // + Xspacing;
3007             Ymain = im->ysize;
3008         }
3009
3010         im->ximg = Xmain + Xylabel;
3011         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3012             im->ximg += Xspacing;
3013         }
3014
3015         if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3016             im->ximg += im->legendwidth;// + Xspacing;
3017         }
3018         if (im->second_axis_scale != 0){
3019             im->ximg += Xylabel;
3020         }
3021
3022         im->ximg += Xvertical + Xvertical2;
3023
3024         if (!(im->extra_flags & NOLEGEND)) {
3025             if(im->legendposition == NORTH || im->legendposition == SOUTH){
3026                 im->legendwidth = im->ximg;
3027                 if (leg_place(im, 0) == -1){
3028                     return -1;
3029                 }
3030             }
3031         }
3032       
3033         im->yimg = Ymain + Yxlabel;
3034         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3035              im->yimg += im->legendheight;
3036         }
3037         
3038         /* reserve space for the title *or* some padding above the graph */
3039         if (Ytitle) {
3040             im->yimg += Ytitle;
3041         } else {
3042             im->yimg += 1.5 * Yspacing;
3043         }
3044         /* reserve space for padding below the graph */
3045         if (im->extra_flags & NOLEGEND) {
3046             im->yimg += Yspacing;
3047         }
3048
3049         if (im->watermark[0] != '\0') {
3050             im->yimg += Ywatermark;
3051         }
3052     }
3053
3054
3055     /* In case of putting the legend in west or east position the first 
3056      ** legend calculation might lead to wrong positions if some items 
3057      ** are not aligned on the left hand side (e.g. centered) as the 
3058      ** legendwidth wight have been increased after the item was placed.
3059      ** In this case the positions have to be recalculated.
3060      */
3061     if (!(im->extra_flags & NOLEGEND)) {
3062         if(im->legendposition == WEST || im->legendposition == EAST){
3063             if (leg_place(im, 0) == -1){
3064                 return -1;
3065             }
3066         }
3067     }
3068
3069     /* After calculating all dimensions
3070      ** it is now possible to calculate 
3071      ** all offsets.
3072      */
3073     switch(im->legendposition){
3074         case NORTH:
3075             im->xOriginTitle   = Xvertical + Xylabel + (im->xsize / 2);
3076             im->yOriginTitle   = 0;
3077
3078             im->xOriginLegend  = 0;
3079             im->yOriginLegend  = Ytitle;
3080
3081             im->xOriginLegendY = 0;
3082             im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3083
3084             im->xorigin        = Xvertical + Xylabel;
3085             im->yorigin        = Ytitle + im->legendheight + Ymain;
3086
3087             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3088             if (im->second_axis_scale != 0){
3089                 im->xOriginLegendY2 += Xylabel;
3090             }
3091             im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3092
3093             break;
3094
3095         case WEST:
3096             im->xOriginTitle   = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3097             im->yOriginTitle   = 0;
3098
3099             im->xOriginLegend  = 0;
3100             im->yOriginLegend  = Ytitle;
3101
3102             im->xOriginLegendY = im->legendwidth;
3103             im->yOriginLegendY = Ytitle + (Ymain / 2);
3104
3105             im->xorigin        = im->legendwidth + Xvertical + Xylabel;
3106             im->yorigin        = Ytitle + Ymain;
3107
3108             im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3109             if (im->second_axis_scale != 0){
3110                 im->xOriginLegendY2 += Xylabel;
3111             }
3112             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3113
3114             break;
3115
3116         case SOUTH:
3117             im->xOriginTitle   = Xvertical + Xylabel + im->xsize / 2;
3118             im->yOriginTitle   = 0;
3119
3120             im->xOriginLegend  = 0;
3121             im->yOriginLegend  = Ytitle + Ymain + Yxlabel;
3122
3123             im->xOriginLegendY = 0;
3124             im->yOriginLegendY = Ytitle + (Ymain / 2);
3125
3126             im->xorigin        = Xvertical + Xylabel;
3127             im->yorigin        = Ytitle + Ymain;
3128
3129             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3130             if (im->second_axis_scale != 0){
3131                 im->xOriginLegendY2 += Xylabel;
3132             }
3133             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3134
3135             break;
3136
3137         case EAST:
3138             im->xOriginTitle   = Xvertical + Xylabel + im->xsize / 2;
3139             im->yOriginTitle   = 0;
3140
3141             im->xOriginLegend  = Xvertical + Xylabel + Xmain + Xvertical2;
3142             if (im->second_axis_scale != 0){
3143                 im->xOriginLegend += Xylabel;
3144             }
3145             im->yOriginLegend  = Ytitle;
3146
3147             im->xOriginLegendY = 0;
3148             im->yOriginLegendY = Ytitle + (Ymain / 2);
3149
3150             im->xorigin        = Xvertical + Xylabel;
3151             im->yorigin        = Ytitle + Ymain;
3152
3153             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3154             if (im->second_axis_scale != 0){
3155                 im->xOriginLegendY2 += Xylabel;
3156             }
3157             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3158
3159             if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3160                 im->xOriginTitle    += Xspacing;
3161                 im->xOriginLegend   += Xspacing;
3162                 im->xOriginLegendY  += Xspacing;
3163                 im->xorigin         += Xspacing;
3164                 im->xOriginLegendY2 += Xspacing;
3165             }
3166             break;
3167     }
3168
3169     xtr(im, 0);
3170     ytr(im, DNAN);
3171     return 0;
3172 }
3173
3174 static cairo_status_t cairo_output(
3175     void *closure,
3176     const unsigned char
3177     *data,
3178     unsigned int length)
3179 {
3180     image_desc_t *im = (image_desc_t*)closure;
3181
3182     im->rendered_image =
3183         (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3184     if (im->rendered_image == NULL)
3185         return CAIRO_STATUS_WRITE_ERROR;
3186     memcpy(im->rendered_image + im->rendered_image_size, data, length);
3187     im->rendered_image_size += length;
3188     return CAIRO_STATUS_SUCCESS;
3189 }
3190
3191 /* draw that picture thing ... */
3192 int graph_paint(
3193     image_desc_t *im)
3194 {
3195     int       i, ii;
3196     int       lazy = lazy_check(im);
3197     double    areazero = 0.0;
3198     graph_desc_t *lastgdes = NULL;
3199     rrd_infoval_t info;
3200
3201 //    PangoFontMap *font_map = pango_cairo_font_map_get_default();
3202
3203     /* if we want and can be lazy ... quit now */
3204     if (lazy) {
3205         info.u_cnt = im->ximg;
3206         grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3207         info.u_cnt = im->yimg;
3208         grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3209         return 0;
3210     }
3211     /* pull the data from the rrd files ... */
3212     if (data_fetch(im) == -1)
3213         return -1;
3214     /* evaluate VDEF and CDEF operations ... */
3215     if (data_calc(im) == -1)
3216         return -1;
3217     /* calculate and PRINT and GPRINT definitions. We have to do it at
3218      * this point because it will affect the length of the legends
3219      * if there are no graph elements (i==0) we stop here ... 
3220      * if we are lazy, try to quit ... 
3221      */
3222     i = print_calc(im);
3223     if (i < 0)
3224         return -1;
3225
3226     if ((i == 0) || lazy)
3227         return 0;
3228
3229 /**************************************************************
3230  *** Calculating sizes and locations became a bit confusing ***
3231  *** so I moved this into a separate function.              ***
3232  **************************************************************/
3233     if (graph_size_location(im, i) == -1)
3234         return -1;
3235
3236     info.u_cnt = im->xorigin;
3237     grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3238     info.u_cnt = im->yorigin - im->ysize;
3239     grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3240     info.u_cnt = im->xsize;
3241     grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3242     info.u_cnt = im->ysize;
3243     grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3244     info.u_cnt = im->ximg;
3245     grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3246     info.u_cnt = im->yimg;
3247     grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3248     info.u_cnt = im->start;
3249     grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3250     info.u_cnt = im->end;
3251     grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3252
3253     /* get actual drawing data and find min and max values */
3254     if (data_proc(im) == -1)
3255         return -1;
3256     if (!im->logarithmic) {
3257         si_unit(im);
3258     }
3259
3260     /* identify si magnitude Kilo, Mega Giga ? */
3261     if (!im->rigid && !im->logarithmic)
3262         expand_range(im);   /* make sure the upper and lower limit are
3263                                sensible values */
3264
3265     info.u_val = im->minval;
3266     grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3267     info.u_val = im->maxval;
3268     grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3269
3270     if (!calc_horizontal_grid(im))
3271         return -1;
3272     /* reset precalc */
3273     ytr(im, DNAN);
3274 /*   if (im->gridfit)
3275      apply_gridfit(im); */
3276     /* the actual graph is created by going through the individual
3277        graph elements and then drawing them */
3278     cairo_surface_destroy(im->surface);
3279     switch (im->imgformat) {
3280     case IF_PNG:
3281         im->surface =
3282             cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3283                                        im->ximg * im->zoom,
3284                                        im->yimg * im->zoom);
3285         break;
3286     case IF_PDF:
3287         im->gridfit = 0;
3288         im->surface = strlen(im->graphfile)
3289             ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3290                                        im->yimg * im->zoom)
3291             : cairo_pdf_surface_create_for_stream
3292             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3293         break;
3294     case IF_EPS:
3295         im->gridfit = 0;
3296         im->surface = strlen(im->graphfile)
3297             ?
3298             cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3299                                     im->yimg * im->zoom)
3300             : cairo_ps_surface_create_for_stream
3301             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3302         break;
3303     case IF_SVG:
3304         im->gridfit = 0;
3305         im->surface = strlen(im->graphfile)
3306             ?
3307             cairo_svg_surface_create(im->
3308                                      graphfile,
3309                                      im->ximg * im->zoom, im->yimg * im->zoom)
3310             : cairo_svg_surface_create_for_stream
3311             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3312         cairo_svg_surface_restrict_to_version
3313             (im->surface, CAIRO_SVG_VERSION_1_1);
3314         break;
3315     };
3316     cairo_destroy(im->cr);
3317     im->cr = cairo_create(im->surface);
3318     cairo_set_antialias(im->cr, im->graph_antialias);
3319     cairo_scale(im->cr, im->zoom, im->zoom);
3320 //    pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3321     gfx_new_area(im, 0, 0, 0, im->yimg,
3322                  im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3323     gfx_add_point(im, im->ximg, 0);
3324     gfx_close_path(im);
3325     gfx_new_area(im, im->xorigin,
3326                  im->yorigin,
3327                  im->xorigin +
3328                  im->xsize, im->yorigin,
3329                  im->xorigin +
3330                  im->xsize,
3331                  im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3332     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3333     gfx_close_path(im);
3334     cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3335                     im->xsize, im->ysize + 2.0);
3336     cairo_clip(im->cr);
3337     if (im->minval > 0.0)
3338         areazero = im->minval;
3339     if (im->maxval < 0.0)
3340         areazero = im->maxval;
3341     for (i = 0; i < im->gdes_c; i++) {
3342         switch (im->gdes[i].gf) {
3343         case GF_CDEF:
3344         case GF_VDEF:
3345         case GF_DEF:
3346         case GF_PRINT:
3347         case GF_GPRINT:
3348         case GF_COMMENT:
3349         case GF_TEXTALIGN:
3350         case GF_HRULE:
3351         case GF_VRULE:
3352         case GF_XPORT:
3353         case GF_SHIFT:
3354             break;
3355         case GF_TICK:
3356             for (ii = 0; ii < im->xsize; ii++) {
3357                 if (!isnan(im->gdes[i].p_data[ii])
3358                     && im->gdes[i].p_data[ii] != 0.0) {
3359                     if (im->gdes[i].yrule > 0) {
3360                         gfx_line(im,
3361                                  im->xorigin + ii,
3362                                  im->yorigin + 1.0,
3363                                  im->xorigin + ii,
3364                                  im->yorigin -
3365                                  im->gdes[i].yrule *
3366                                  im->ysize, 1.0, im->gdes[i].col);
3367                     } else if (im->gdes[i].yrule < 0) {
3368                         gfx_line(im,
3369                                  im->xorigin + ii,
3370                                  im->yorigin - im->ysize - 1.0,
3371                                  im->xorigin + ii,
3372                                  im->yorigin - im->ysize -
3373                                                 im->gdes[i].
3374                                                 yrule *
3375                                  im->ysize, 1.0, im->gdes[i].col);
3376                     }
3377                 }
3378             }
3379             break;
3380         case GF_LINE:
3381         case GF_AREA:
3382             /* fix data points at oo and -oo */
3383             for (ii = 0; ii < im->xsize; ii++) {
3384                 if (isinf(im->gdes[i].p_data[ii])) {
3385                     if (im->gdes[i].p_data[ii] > 0) {
3386                         im->gdes[i].p_data[ii] = im->maxval;
3387                     } else {
3388                         im->gdes[i].p_data[ii] = im->minval;
3389                     }
3390
3391                 }
3392             }           /* for */
3393
3394             /* *******************************************************
3395                a           ___. (a,t) 
3396                |   |    ___
3397                ____|   |   |   |
3398                |       |___|
3399                -------|--t-1--t--------------------------------      
3400
3401                if we know the value at time t was a then 
3402                we draw a square from t-1 to t with the value a.
3403
3404                ********************************************************* */
3405             if (im->gdes[i].col.alpha != 0.0) {
3406                 /* GF_LINE and friend */
3407                 if (im->gdes[i].gf == GF_LINE) {
3408                     double    last_y = 0.0;
3409                     int       draw_on = 0;
3410
3411                     cairo_save(im->cr);
3412                     cairo_new_path(im->cr);
3413                     cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3414                     if (im->gdes[i].dash) {
3415                         cairo_set_dash(im->cr,
3416                                        im->gdes[i].p_dashes,
3417                                        im->gdes[i].ndash, im->gdes[i].offset);
3418                     }
3419
3420                     for (ii = 1; ii < im->xsize; ii++) {
3421                         if (isnan(im->gdes[i].p_data[ii])
3422                             || (im->slopemode == 1
3423                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3424                             draw_on = 0;
3425                             continue;
3426                         }
3427                         if (draw_on == 0) {
3428                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3429                             if (im->slopemode == 0) {
3430                                 double    x = ii - 1 + im->xorigin;
3431                                 double    y = last_y;
3432
3433                                 gfx_line_fit(im, &x, &y);
3434                                 cairo_move_to(im->cr, x, y);
3435                                 x = ii + im->xorigin;
3436                                 y = last_y;
3437                                 gfx_line_fit(im, &x, &y);
3438                                 cairo_line_to(im->cr, x, y);
3439                             } else {
3440                                 double    x = ii - 1 + im->xorigin;
3441                                 double    y =
3442                                     ytr(im, im->gdes[i].p_data[ii - 1]);
3443                                 gfx_line_fit(im, &x, &y);
3444                                 cairo_move_to(im->cr, x, y);
3445                                 x = ii + im->xorigin;
3446                                 y = last_y;
3447                                 gfx_line_fit(im, &x, &y);
3448                                 cairo_line_to(im->cr, x, y);
3449                             }
3450                             draw_on = 1;
3451                         } else {
3452                             double    x1 = ii + im->xorigin;
3453                             double    y1 = ytr(im, im->gdes[i].p_data[ii]);
3454
3455                             if (im->slopemode == 0
3456                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3457                                 double    x = ii - 1 + im->xorigin;
3458                                 double    y = y1;
3459
3460                                 gfx_line_fit(im, &x, &y);
3461                                 cairo_line_to(im->cr, x, y);
3462                             };
3463                             last_y = y1;
3464                             gfx_line_fit(im, &x1, &y1);
3465                             cairo_line_to(im->cr, x1, y1);
3466                         };
3467                     }
3468                     cairo_set_source_rgba(im->cr,
3469                                           im->gdes[i].
3470                                           col.red,
3471                                           im->gdes[i].
3472                                           col.green,
3473                                           im->gdes[i].
3474                                           col.blue, im->gdes[i].col.alpha);
3475                     cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3476                     cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3477                     cairo_stroke(im->cr);
3478                     cairo_restore(im->cr);
3479                 } else {
3480                     int       idxI = -1;
3481                     double   *foreY =
3482                         (double *) malloc(sizeof(double) * im->xsize * 2);
3483                     double   *foreX =
3484                         (double *) malloc(sizeof(double) * im->xsize * 2);
3485                     double   *backY =
3486                         (double *) malloc(sizeof(double) * im->xsize * 2);
3487                     double   *backX =
3488                         (double *) malloc(sizeof(double) * im->xsize * 2);
3489                     int       drawem = 0;
3490
3491                     for (ii = 0; ii <= im->xsize; ii++) {
3492                         double    ybase, ytop;
3493
3494                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3495                             int       cntI = 1;
3496                             int       lastI = 0;
3497
3498                             while (cntI < idxI
3499                                    &&
3500                                    AlmostEqual2sComplement(foreY
3501                                                            [lastI],
3502                                                            foreY[cntI], 4)
3503                                    &&
3504                                    AlmostEqual2sComplement(foreY
3505                                                            [lastI],
3506                                                            foreY
3507                                                            [cntI + 1], 4)) {
3508                                 cntI++;
3509                             }
3510                             gfx_new_area(im,
3511                                          backX[0], backY[0],
3512                                          foreX[0], foreY[0],
3513                                          foreX[cntI],
3514                                          foreY[cntI], im->gdes[i].col);
3515                             while (cntI < idxI) {
3516                                 lastI = cntI;
3517                                 cntI++;
3518                                 while (cntI < idxI
3519                                        &&
3520                                        AlmostEqual2sComplement(foreY
3521                                                                [lastI],
3522                                                                foreY[cntI], 4)
3523                                        &&
3524                                        AlmostEqual2sComplement(foreY
3525                                                                [lastI],
3526                                                                foreY
3527                                                                [cntI
3528                                                                 + 1], 4)) {
3529                                     cntI++;
3530                                 }
3531                                 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3532                             }
3533                             gfx_add_point(im, backX[idxI], backY[idxI]);
3534                             while (idxI > 1) {
3535                                 lastI = idxI;
3536                                 idxI--;
3537                                 while (idxI > 1
3538                                        &&
3539                                        AlmostEqual2sComplement(backY
3540                                                                [lastI],
3541                                                                backY[idxI], 4)
3542                                        &&
3543                                        AlmostEqual2sComplement(backY
3544                                                                [lastI],
3545                                                                backY
3546                                                                [idxI
3547                                                                 - 1], 4)) {
3548                                     idxI--;
3549                                 }
3550                                 gfx_add_point(im, backX[idxI], backY[idxI]);
3551                             }
3552                             idxI = -1;
3553                             drawem = 0;
3554                             gfx_close_path(im);
3555                         }
3556                         if (drawem != 0) {
3557                             drawem = 0;
3558                             idxI = -1;
3559                         }
3560                         if (ii == im->xsize)
3561                             break;
3562                         if (im->slopemode == 0 && ii == 0) {
3563                             continue;
3564                         }
3565                         if (isnan(im->gdes[i].p_data[ii])) {
3566                             drawem = 1;
3567                             continue;
3568                         }
3569                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3570                         if (lastgdes && im->gdes[i].stack) {
3571                             ybase = ytr(im, lastgdes->p_data[ii]);
3572                         } else {
3573                             ybase = ytr(im, areazero);
3574                         }
3575                         if (ybase == ytop) {
3576                             drawem = 1;
3577                             continue;
3578                         }
3579
3580                         if (ybase > ytop) {
3581                             double    extra = ytop;
3582
3583                             ytop = ybase;
3584                             ybase = extra;
3585                         }
3586                         if (im->slopemode == 0) {
3587                             backY[++idxI] = ybase - 0.2;
3588                             backX[idxI] = ii + im->xorigin - 1;
3589                             foreY[idxI] = ytop + 0.2;
3590                             foreX[idxI] = ii + im->xorigin - 1;
3591                         }
3592                         backY[++idxI] = ybase - 0.2;
3593                         backX[idxI] = ii + im->xorigin;
3594                         foreY[idxI] = ytop + 0.2;
3595                         foreX[idxI] = ii + im->xorigin;
3596                     }
3597                     /* close up any remaining area */
3598                     free(foreY);
3599                     free(foreX);
3600                     free(backY);
3601                     free(backX);
3602                 }       /* else GF_LINE */
3603             }
3604             /* if color != 0x0 */
3605             /* make sure we do not run into trouble when stacking on NaN */
3606             for (ii = 0; ii < im->xsize; ii++) {
3607                 if (isnan(im->gdes[i].p_data[ii])) {
3608                     if (lastgdes && (im->gdes[i].stack)) {
3609                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3610                     } else {
3611                         im->gdes[i].p_data[ii] = areazero;
3612                     }
3613                 }
3614             }
3615             lastgdes = &(im->gdes[i]);
3616             break;
3617         case GF_STACK:
3618             rrd_set_error
3619                 ("STACK should already be turned into LINE or AREA here");
3620             return -1;
3621             break;
3622         }               /* switch */
3623     }
3624     cairo_reset_clip(im->cr);
3625
3626     /* grid_paint also does the text */
3627     if (!(im->extra_flags & ONLY_GRAPH))
3628         grid_paint(im);
3629     if (!(im->extra_flags & ONLY_GRAPH))
3630         axis_paint(im);
3631     /* the RULES are the last thing to paint ... */
3632     for (i = 0; i < im->gdes_c; i++) {
3633
3634         switch (im->gdes[i].gf) {
3635         case GF_HRULE:
3636             if (im->gdes[i].yrule >= im->minval
3637                 && im->gdes[i].yrule <= im->maxval) {
3638                 cairo_save(im->cr);
3639                 if (im->gdes[i].dash) {
3640                     cairo_set_dash(im->cr,
3641                                    im->gdes[i].p_dashes,
3642                                    im->gdes[i].ndash, im->gdes[i].offset);
3643                 }
3644                 gfx_line(im, im->xorigin,
3645                          ytr(im, im->gdes[i].yrule),
3646                          im->xorigin + im->xsize,
3647                          ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3648                 cairo_stroke(im->cr);
3649                 cairo_restore(im->cr);
3650             }
3651             break;
3652         case GF_VRULE:
3653             if (im->gdes[i].xrule >= im->start
3654                 && im->gdes[i].xrule <= im->end) {
3655                 cairo_save(im->cr);
3656                 if (im->gdes[i].dash) {
3657                     cairo_set_dash(im->cr,
3658                                    im->gdes[i].p_dashes,
3659                                    im->gdes[i].ndash, im->gdes[i].offset);
3660                 }
3661                 gfx_line(im,
3662                          xtr(im, im->gdes[i].xrule),
3663                          im->yorigin, xtr(im,
3664                                           im->
3665                                           gdes[i].
3666                                           xrule),
3667                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3668                 cairo_stroke(im->cr);
3669                 cairo_restore(im->cr);
3670             }
3671             break;
3672         default:
3673             break;
3674         }
3675     }
3676
3677
3678     switch (im->imgformat) {
3679     case IF_PNG:
3680     {
3681         cairo_status_t status;
3682
3683         status = strlen(im->graphfile) ?
3684             cairo_surface_write_to_png(im->surface, im->graphfile)
3685             : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3686                                                 im);
3687
3688         if (status != CAIRO_STATUS_SUCCESS) {
3689             rrd_set_error("Could not save png to '%s'", im->graphfile);
3690             return 1;
3691         }
3692         break;
3693     }
3694     default:
3695         if (strlen(im->graphfile)) {
3696             cairo_show_page(im->cr);
3697         } else {
3698             cairo_surface_finish(im->surface);
3699         }
3700         break;
3701     }
3702
3703     return 0;
3704 }
3705
3706
3707 /*****************************************************
3708  * graph stuff 
3709  *****************************************************/
3710
3711 int gdes_alloc(
3712     image_desc_t *im)
3713 {
3714
3715     im->gdes_c++;
3716     if ((im->gdes = (graph_desc_t *)
3717          rrd_realloc(im->gdes, (im->gdes_c)
3718                      * sizeof(graph_desc_t))) == NULL) {
3719         rrd_set_error("realloc graph_descs");
3720         return -1;
3721     }
3722
3723
3724     im->gdes[im->gdes_c - 1].step = im->step;
3725     im->gdes[im->gdes_c - 1].step_orig = im->step;
3726     im->gdes[im->gdes_c - 1].stack = 0;
3727     im->gdes[im->gdes_c - 1].linewidth = 0;
3728     im->gdes[im->gdes_c - 1].debug = 0;
3729     im->gdes[im->gdes_c - 1].start = im->start;
3730     im->gdes[im->gdes_c - 1].start_orig = im->start;
3731     im->gdes[im->gdes_c - 1].end = im->end;
3732     im->gdes[im->gdes_c - 1].end_orig = im->end;
3733     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3734     im->gdes[im->gdes_c - 1].data = NULL;
3735     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3736     im->gdes[im->gdes_c - 1].data_first = 0;
3737     im->gdes[im->gdes_c - 1].p_data = NULL;
3738     im->gdes[im->gdes_c - 1].rpnp = NULL;
3739     im->gdes[im->gdes_c - 1].p_dashes = NULL;
3740     im->gdes[im->gdes_c - 1].shift = 0.0;
3741     im->gdes[im->gdes_c - 1].dash = 0;
3742     im->gdes[im->gdes_c - 1].ndash = 0;
3743     im->gdes[im->gdes_c - 1].offset = 0;
3744     im->gdes[im->gdes_c - 1].col.red = 0.0;
3745     im->gdes[im->gdes_c - 1].col.green = 0.0;
3746     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3747     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3748     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3749     im->gdes[im->gdes_c - 1].format[0] = '\0';
3750     im->gdes[im->gdes_c - 1].strftm = 0;
3751     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3752     im->gdes[im->gdes_c - 1].ds = -1;
3753     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3754     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3755     im->gdes[im->gdes_c - 1].yrule = DNAN;
3756     im->gdes[im->gdes_c - 1].xrule = 0;
3757     return 0;
3758 }
3759
3760 /* copies input untill the first unescaped colon is found
3761    or until input ends. backslashes have to be escaped as well */
3762 int scan_for_col(
3763     const char *const input,
3764     int len,
3765     char *const output)
3766 {
3767     int       inp, outp = 0;
3768
3769     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3770         if (input[inp] == '\\'
3771             && input[inp + 1] != '\0'
3772             && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3773             output[outp++] = input[++inp];
3774         } else {
3775             output[outp++] = input[inp];
3776         }
3777     }
3778     output[outp] = '\0';
3779     return inp;
3780 }
3781
3782 /* Now just a wrapper around rrd_graph_v */
3783 int rrd_graph(
3784     int argc,
3785     char **argv,
3786     char ***prdata,
3787     int *xsize,
3788     int *ysize,
3789     FILE * stream,
3790     double *ymin,
3791     double *ymax)
3792 {
3793     int       prlines = 0;
3794     rrd_info_t *grinfo = NULL;
3795     rrd_info_t *walker;
3796
3797     grinfo = rrd_graph_v(argc, argv);
3798     if (grinfo == NULL)
3799         return -1;
3800     walker = grinfo;
3801     (*prdata) = NULL;
3802     while (walker) {
3803         if (strcmp(walker->key, "image_info") == 0) {
3804             prlines++;
3805             if (((*prdata) =
3806                  (char**)rrd_realloc((*prdata),
3807                              (prlines + 1) * sizeof(char *))) == NULL) {
3808                 rrd_set_error("realloc prdata");
3809                 return 0;
3810             }
3811             /* imginfo goes to position 0 in the prdata array */
3812             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3813                                              + 2) * sizeof(char));
3814             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3815             (*prdata)[prlines] = NULL;
3816         }
3817         /* skip anything else */
3818         walker = walker->next;
3819     }
3820     walker = grinfo;
3821     *xsize = 0;
3822     *ysize = 0;
3823     *ymin = 0;
3824     *ymax = 0;
3825     while (walker) {
3826         if (strcmp(walker->key, "image_width") == 0) {
3827             *xsize = walker->value.u_cnt;
3828         } else if (strcmp(walker->key, "image_height") == 0) {
3829             *ysize = walker->value.u_cnt;
3830         } else if (strcmp(walker->key, "value_min") == 0) {
3831             *ymin = walker->value.u_val;
3832         } else if (strcmp(walker->key, "value_max") == 0) {
3833             *ymax = walker->value.u_val;
3834         } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3835             prlines++;
3836             if (((*prdata) =
3837                  (char**)rrd_realloc((*prdata),
3838                              (prlines + 1) * sizeof(char *))) == NULL) {
3839                 rrd_set_error("realloc prdata");
3840                 return 0;
3841             }
3842             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3843                                              + 2) * sizeof(char));
3844             (*prdata)[prlines] = NULL;
3845             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3846         } else if (strcmp(walker->key, "image") == 0) {
3847             if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3848                    (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3849                 rrd_set_error("writing image");
3850                 return 0;
3851             }
3852         }
3853         /* skip anything else */
3854         walker = walker->next;
3855     }
3856     rrd_info_free(grinfo);
3857     return 0;
3858 }
3859
3860
3861 /* Some surgery done on this function, it became ridiculously big.
3862 ** Things moved:
3863 ** - initializing     now in rrd_graph_init()
3864 ** - options parsing  now in rrd_graph_options()
3865 ** - script parsing   now in rrd_graph_script()
3866 */
3867 rrd_info_t *rrd_graph_v(
3868     int argc,
3869     char **argv)
3870 {
3871     image_desc_t im;
3872     rrd_info_t *grinfo;
3873     rrd_graph_init(&im);
3874     /* a dummy surface so that we can measure text sizes for placements */
3875     
3876     rrd_graph_options(argc, argv, &im);
3877     if (rrd_test_error()) {
3878         rrd_info_free(im.grinfo);
3879         im_free(&im);
3880         return NULL;
3881     }
3882
3883     if (optind >= argc) {
3884         rrd_info_free(im.grinfo);
3885         im_free(&im);
3886         rrd_set_error("missing filename");
3887         return NULL;
3888     }
3889
3890     if (strlen(argv[optind]) >= MAXPATH) {
3891         rrd_set_error("filename (including path) too long");
3892         rrd_info_free(im.grinfo);
3893         im_free(&im);
3894         return NULL;
3895     }
3896
3897     strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3898     im.graphfile[MAXPATH - 1] = '\0';
3899
3900     if (strcmp(im.graphfile, "-") == 0) {
3901         im.graphfile[0] = '\0';
3902     }
3903
3904     rrd_graph_script(argc, argv, &im, 1);
3905     if (rrd_test_error()) {
3906         rrd_info_free(im.grinfo);
3907         im_free(&im);
3908         return NULL;
3909     }
3910
3911     /* Everything is now read and the actual work can start */
3912
3913     if (graph_paint(&im) == -1) {
3914         rrd_info_free(im.grinfo);
3915         im_free(&im);
3916         return NULL;
3917     }
3918
3919
3920     /* The image is generated and needs to be output.
3921      ** Also, if needed, print a line with information about the image.
3922      */
3923
3924     if (im.imginfo) {
3925         rrd_infoval_t info;
3926         char     *path;
3927         char     *filename;
3928
3929         path = strdup(im.graphfile);
3930         filename = basename(path);
3931         info.u_str =
3932             sprintf_alloc(im.imginfo,
3933                           filename,
3934                           (long) (im.zoom *
3935                                   im.ximg), (long) (im.zoom * im.yimg));
3936         grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3937         free(info.u_str);
3938         free(path);
3939     }
3940     if (im.rendered_image) {
3941         rrd_infoval_t img;
3942
3943         img.u_blo.size = im.rendered_image_size;
3944         img.u_blo.ptr = im.rendered_image;
3945         grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3946     }
3947     grinfo = im.grinfo;
3948     im_free(&im);
3949     return grinfo;
3950 }
3951
3952 static void 
3953 rrd_set_font_desc (
3954     image_desc_t *im,int prop,char *font, double size ){
3955     if (font){
3956         strncpy(im->text_prop[prop].font, font, sizeof(text_prop[prop].font) - 1);        
3957         im->text_prop[prop].font[sizeof(text_prop[prop].font) - 1] = '\0';   
3958         im->text_prop[prop].font_desc = pango_font_description_from_string( font );        
3959     };
3960     if (size > 0){  
3961         im->text_prop[prop].size = size;
3962     };
3963     if (im->text_prop[prop].font_desc && im->text_prop[prop].size ){
3964         pango_font_description_set_size(im->text_prop[prop].font_desc, im->text_prop[prop].size * PANGO_SCALE);
3965     };
3966 }
3967
3968 void rrd_graph_init(
3969     image_desc_t
3970     *im)
3971 {
3972     unsigned int i;
3973     char     *deffont = getenv("RRD_DEFAULT_FONT");
3974     static PangoFontMap *fontmap = NULL;
3975     PangoContext *context;
3976
3977 #ifdef HAVE_TZSET
3978     tzset();
3979 #endif
3980 #ifdef HAVE_SETLOCALE
3981     setlocale(LC_TIME, "");
3982 #ifdef HAVE_MBSTOWCS
3983     setlocale(LC_CTYPE, "");
3984 #endif
3985 #endif
3986     im->base = 1000;
3987     im->daemon_addr = NULL;
3988     im->draw_x_grid = 1;
3989     im->draw_y_grid = 1;
3990     im->extra_flags = 0;
3991     im->font_options = cairo_font_options_create();
3992     im->forceleftspace = 0;
3993     im->gdes_c = 0;
3994     im->gdes = NULL;
3995     im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3996     im->grid_dash_off = 1;
3997     im->grid_dash_on = 1;
3998     im->gridfit = 1;
3999     im->grinfo = (rrd_info_t *) NULL;
4000     im->grinfo_current = (rrd_info_t *) NULL;
4001     im->imgformat = IF_PNG;
4002     im->imginfo = NULL;
4003     im->lazy = 0;
4004     im->legenddirection = TOP_DOWN;
4005     im->legendheight = 0;
4006     im->legendposition = SOUTH;
4007     im->legendwidth = 0;
4008     im->logarithmic = 0;
4009     im->maxval = DNAN;
4010     im->minval = 0;
4011     im->minval = DNAN;
4012     im->prt_c = 0;
4013     im->rigid = 0;
4014     im->rendered_image_size = 0;
4015     im->rendered_image = NULL;
4016     im->slopemode = 0;
4017     im->step = 0;
4018     im->symbol = ' ';
4019     im->tabwidth = 40.0;
4020     im->title[0] = '\0';
4021     im->unitsexponent = 9999;
4022     im->unitslength = 6;
4023     im->viewfactor = 1.0;
4024     im->watermark[0] = '\0';
4025     im->with_markup = 0;
4026     im->ximg = 0;
4027     im->xlab_user.minsec = -1;
4028     im->xorigin = 0;
4029     im->xOriginLegend = 0;
4030     im->xOriginLegendY = 0;
4031     im->xOriginLegendY2 = 0;
4032     im->xOriginTitle = 0;
4033     im->xsize = 400;
4034     im->ygridstep = DNAN;
4035     im->yimg = 0;
4036     im->ylegend[0] = '\0';
4037     im->second_axis_scale = 0; /* 0 disables it */
4038     im->second_axis_shift = 0; /* no shift by default */
4039     im->second_axis_legend[0] = '\0';
4040     im->second_axis_format[0] = '\0'; 
4041     im->yorigin = 0;
4042     im->yOriginLegend = 0;
4043     im->yOriginLegendY = 0;
4044     im->yOriginLegendY2 = 0;
4045     im->yOriginTitle = 0;
4046     im->ysize = 100;
4047     im->zoom = 1;
4048
4049     im->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);     
4050     im->cr = cairo_create(im->surface);
4051
4052     for (i = 0; i < DIM(text_prop); i++) {
4053         im->text_prop[i].size = -1;
4054         rrd_set_font_desc(im,i, deffont ? deffont : text_prop[i].font,text_prop[i].size);
4055     }
4056
4057     if (fontmap == NULL){
4058         fontmap = pango_cairo_font_map_get_default();
4059     }
4060
4061     context =  pango_cairo_font_map_create_context((PangoCairoFontMap*)fontmap);
4062
4063     pango_cairo_context_set_resolution(context, 100);
4064
4065     pango_cairo_update_context(im->cr,context);
4066
4067     im->layout = pango_layout_new(context);
4068
4069 //  im->layout = pango_cairo_create_layout(im->cr);
4070
4071
4072     cairo_font_options_set_hint_style
4073         (im->font_options, CAIRO_HINT_STYLE_FULL);
4074     cairo_font_options_set_hint_metrics
4075         (im->font_options, CAIRO_HINT_METRICS_ON);
4076     cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
4077
4078
4079
4080     for (i = 0; i < DIM(graph_col); i++)
4081         im->graph_col[i] = graph_col[i];
4082
4083
4084 }
4085
4086
4087 void rrd_graph_options(
4088     int argc,
4089     char *argv[],
4090     image_desc_t
4091     *im)
4092 {
4093     int       stroff;
4094     char     *parsetime_error = NULL;
4095     char      scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
4096     time_t    start_tmp = 0, end_tmp = 0;
4097     long      long_tmp;
4098     rrd_time_value_t start_tv, end_tv;
4099     long unsigned int color;
4100     char     *old_locale = "";
4101
4102     /* defines for long options without a short equivalent. should be bytes,
4103        and may not collide with (the ASCII value of) short options */
4104 #define LONGOPT_UNITS_SI 255
4105
4106 /* *INDENT-OFF* */
4107     struct option long_options[] = {
4108         { "start",              required_argument, 0, 's'}, 
4109         { "end",                required_argument, 0, 'e'},
4110         { "x-grid",             required_argument, 0, 'x'},
4111         { "y-grid",             required_argument, 0, 'y'},
4112         { "vertical-label",     required_argument, 0, 'v'},
4113         { "width",              required_argument, 0, 'w'},
4114         { "height",             required_argument, 0, 'h'},
4115         { "full-size-mode",     no_argument,       0, 'D'},
4116         { "interlaced",         no_argument,       0, 'i'},
4117         { "upper-limit",        required_argument, 0, 'u'},
4118         { "lower-limit",        required_argument, 0, 'l'},
4119         { "rigid",              no_argument,       0, 'r'},
4120         { "base",               required_argument, 0, 'b'},
4121         { "logarithmic",        no_argument,       0, 'o'},
4122         { "color",              required_argument, 0, 'c'},
4123         { "font",               required_argument, 0, 'n'},
4124         { "title",              required_argument, 0, 't'},
4125         { "imginfo",            required_argument, 0, 'f'},
4126         { "imgformat",          required_argument, 0, 'a'},
4127         { "lazy",               no_argument,       0, 'z'},
4128         { "zoom",               required_argument, 0, 'm'},
4129         { "no-legend",          no_argument,       0, 'g'},
4130         { "legend-position",    required_argument, 0, 1005},
4131         { "legend-direction",   required_argument, 0, 1006},
4132         { "force-rules-legend", no_argument,       0, 'F'},
4133         { "only-graph",         no_argument,       0, 'j'},
4134         { "alt-y-grid",         no_argument,       0, 'Y'},
4135         {"disable-rrdtool-tag", no_argument,       0,  1001},
4136         {"right-axis",          required_argument, 0,  1002},
4137         {"right-axis-label",    required_argument, 0,  1003},
4138         {"right-axis-format",   required_argument, 0,  1004},     
4139         { "no-minor",           no_argument,       0, 'I'}, 
4140         { "slope-mode",         no_argument,       0, 'E'},
4141         { "alt-autoscale",      no_argument,       0, 'A'},
4142         { "alt-autoscale-min",  no_argument,       0, 'J'},
4143         { "alt-autoscale-max",  no_argument,       0, 'M'},
4144         { "no-gridfit",         no_argument,       0, 'N'},
4145         { "units-exponent",     required_argument, 0, 'X'},
4146         { "units-length",       required_argument, 0, 'L'},
4147         { "units",              required_argument, 0, LONGOPT_UNITS_SI},
4148         { "step",               required_argument, 0, 'S'},
4149         { "tabwidth",           required_argument, 0, 'T'},
4150         { "font-render-mode",   required_argument, 0, 'R'},
4151         { "graph-render-mode",  required_argument, 0, 'G'},
4152         { "font-smoothing-threshold", required_argument, 0, 'B'},
4153         { "watermark",          required_argument, 0, 'W'},
4154         { "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 */
4155         { "pango-markup",       no_argument,       0, 'P'},
4156         { "daemon",             required_argument, 0, 'd'},
4157         {  0, 0, 0, 0}
4158 };
4159 /* *INDENT-ON* */
4160
4161     optind = 0;
4162     opterr = 0;         /* initialize getopt */
4163     rrd_parsetime("end-24h", &start_tv);
4164     rrd_parsetime("now", &end_tv);
4165     while (1) {
4166         int       option_index = 0;
4167         int       opt;
4168         int       col_start, col_end;
4169
4170         opt = getopt_long(argc, argv,
4171                           "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:",
4172                           long_options, &option_index);
4173         if (opt == EOF)
4174             break;
4175         switch (opt) {
4176         case 'I':
4177             im->extra_flags |= NOMINOR;
4178             break;
4179         case 'Y':
4180             im->extra_flags |= ALTYGRID;
4181             break;
4182         case 'A':
4183             im->extra_flags |= ALTAUTOSCALE;
4184             break;
4185         case 'J':
4186             im->extra_flags |= ALTAUTOSCALE_MIN;
4187             break;
4188         case 'M':
4189             im->extra_flags |= ALTAUTOSCALE_MAX;
4190             break;
4191         case 'j':
4192             im->extra_flags |= ONLY_GRAPH;
4193             break;
4194         case 'g':
4195             im->extra_flags |= NOLEGEND;
4196             break;
4197         case 1005:
4198             if (strcmp(optarg, "north") == 0) {
4199                 im->legendposition = NORTH;
4200             } else if (strcmp(optarg, "west") == 0) {
4201                 im->legendposition = WEST;
4202             } else if (strcmp(optarg, "south") == 0) {
4203                 im->legendposition = SOUTH;
4204             } else if (strcmp(optarg, "east") == 0) {
4205                 im->legendposition = EAST;
4206             } else {
4207                 rrd_set_error("unknown legend-position '%s'", optarg);
4208                 return;
4209             }
4210             break;
4211         case 1006:
4212             if (strcmp(optarg, "topdown") == 0) {
4213                 im->legenddirection = TOP_DOWN;
4214             } else if (strcmp(optarg, "bottomup") == 0) {
4215                 im->legenddirection = BOTTOM_UP;
4216             } else {
4217                 rrd_set_error("unknown legend-position '%s'", optarg);
4218                 return;
4219             }
4220             break;
4221         case 'F':
4222             im->extra_flags |= FORCE_RULES_LEGEND;
4223             break;
4224         case 1001:
4225             im->extra_flags |= NO_RRDTOOL_TAG;
4226             break;              
4227         case LONGOPT_UNITS_SI:
4228             if (im->extra_flags & FORCE_UNITS) {
4229                 rrd_set_error("--units can only be used once!");
4230                 setlocale(LC_NUMERIC, old_locale);
4231                 return;
4232             }
4233             if (strcmp(optarg, "si") == 0)
4234                 im->extra_flags |= FORCE_UNITS_SI;
4235             else {
4236                 rrd_set_error("invalid argument for --units: %s", optarg);
4237                 return;
4238             }
4239             break;
4240         case 'X':
4241             im->unitsexponent = atoi(optarg);
4242             break;
4243         case 'L':
4244             im->unitslength = atoi(optarg);
4245             im->forceleftspace = 1;
4246             break;
4247         case 'T':
4248             old_locale = setlocale(LC_NUMERIC, "C");
4249             im->tabwidth = atof(optarg);
4250             setlocale(LC_NUMERIC, old_locale);
4251             break;
4252         case 'S':
4253             old_locale = setlocale(LC_NUMERIC, "C");
4254             im->step = atoi(optarg);
4255             setlocale(LC_NUMERIC, old_locale);
4256             break;
4257         case 'N':
4258             im->gridfit = 0;
4259             break;
4260         case 'P':
4261             im->with_markup = 1;
4262             break;
4263         case 's':
4264             if ((parsetime_error = rrd_parsetime(optarg, &start_tv))) {
4265                 rrd_set_error("start time: %s", parsetime_error);
4266                 return;
4267             }
4268             break;
4269         case 'e':
4270             if ((parsetime_error = rrd_parsetime(optarg, &end_tv))) {
4271                 rrd_set_error("end time: %s", parsetime_error);
4272                 return;
4273             }
4274             break;
4275         case 'x':
4276             if (strcmp(optarg, "none") == 0) {
4277                 im->draw_x_grid = 0;
4278                 break;
4279             };
4280             if (sscanf(optarg,
4281                        "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4282                        scan_gtm,
4283                        &im->xlab_user.gridst,
4284                        scan_mtm,
4285                        &im->xlab_user.mgridst,
4286                        scan_ltm,
4287                        &im->xlab_user.labst,
4288                        &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4289                 strncpy(im->xlab_form, optarg + stroff,
4290                         sizeof(im->xlab_form) - 1);
4291                 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4292                 if ((int)
4293                     (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4294                     rrd_set_error("unknown keyword %s", scan_gtm);
4295                     return;
4296                 } else if ((int)
4297                            (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4298                            == -1) {
4299                     rrd_set_error("unknown keyword %s", scan_mtm);
4300                     return;
4301                 } else if ((int)
4302                            (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4303                     rrd_set_error("unknown keyword %s", scan_ltm);
4304                     return;
4305                 }
4306                 im->xlab_user.minsec = 1;
4307                 im->xlab_user.stst = im->xlab_form;
4308             } else {
4309                 rrd_set_error("invalid x-grid format");
4310                 return;
4311             }
4312             break;
4313         case 'y':
4314
4315             if (strcmp(optarg, "none") == 0) {
4316                 im->draw_y_grid = 0;
4317                 break;
4318             };
4319             old_locale = setlocale(LC_NUMERIC, "C");
4320             if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4321                 setlocale(LC_NUMERIC, old_locale);
4322                 if (im->ygridstep <= 0) {
4323                     rrd_set_error("grid step must be > 0");
4324                     return;
4325                 } else if (im->ylabfact < 1) {
4326                     rrd_set_error("label factor must be > 0");
4327                     return;
4328                 }
4329             } else {
4330                 setlocale(LC_NUMERIC, old_locale);
4331                 rrd_set_error("invalid y-grid format");
4332                 return;
4333             }
4334             break;
4335         case 1002: /* right y axis */
4336
4337             if(sscanf(optarg,
4338                       "%lf:%lf",
4339                       &im->second_axis_scale,
4340                       &im->second_axis_shift) == 2) {
4341                 if(im->second_axis_scale==0){
4342                     rrd_set_error("the second_axis_scale  must not be 0");
4343                     return;
4344                 }
4345             } else {
4346                 rrd_set_error("invalid right-axis format expected scale:shift");
4347                 return;
4348             }
4349             break;
4350         case 1003:
4351             strncpy(im->second_axis_legend,optarg,150);
4352             im->second_axis_legend[150]='\0';
4353             break;
4354         case 1004:
4355             if (bad_format(optarg)){
4356                 rrd_set_error("use either %le or %lf formats");
4357                 return;
4358             }
4359             strncpy(im->second_axis_format,optarg,150);
4360             im->second_axis_format[150]='\0';
4361             break;
4362         case 'v':
4363             strncpy(im->ylegend, optarg, 150);
4364             im->ylegend[150] = '\0';
4365             break;
4366         case 'u':
4367             old_locale = setlocale(LC_NUMERIC, "C");
4368             im->maxval = atof(optarg);
4369             setlocale(LC_NUMERIC, old_locale);
4370             break;
4371         case 'l':
4372             old_locale = setlocale(LC_NUMERIC, "C");
4373             im->minval = atof(optarg);
4374             setlocale(LC_NUMERIC, old_locale);
4375             break;
4376         case 'b':
4377             im->base = atol(optarg);
4378             if (im->base != 1024 && im->base != 1000) {
4379                 rrd_set_error
4380                     ("the only sensible value for base apart from 1000 is 1024");
4381                 return;
4382             }
4383             break;
4384         case 'w':
4385             long_tmp = atol(optarg);
4386             if (long_tmp < 10) {
4387                 rrd_set_error("width below 10 pixels");
4388                 return;
4389             }
4390             im->xsize = long_tmp;
4391             break;
4392         case 'h':
4393             long_tmp = atol(optarg);
4394             if (long_tmp < 10) {
4395                 rrd_set_error("height below 10 pixels");
4396                 return;
4397             }
4398             im->ysize = long_tmp;
4399             break;
4400         case 'D':
4401             im->extra_flags |= FULL_SIZE_MODE;
4402             break;
4403         case 'i':
4404             /* interlaced png not supported at the moment */
4405             break;
4406         case 'r':
4407             im->rigid = 1;
4408             break;
4409         case 'f':
4410             im->imginfo = optarg;
4411             break;
4412         case 'a':
4413             if ((int)
4414                 (im->imgformat = if_conv(optarg)) == -1) {
4415                 rrd_set_error("unsupported graphics format '%s'", optarg);
4416                 return;
4417             }
4418             break;
4419         case 'z':
4420             im->lazy = 1;
4421             break;
4422         case 'E':
4423             im->slopemode = 1;
4424             break;
4425         case 'o':
4426             im->logarithmic = 1;
4427             break;
4428         case 'c':
4429             if (sscanf(optarg,
4430                        "%10[A-Z]#%n%8lx%n",
4431                        col_nam, &col_start, &color, &col_end) == 2) {
4432                 int       ci;
4433                 int       col_len = col_end - col_start;
4434
4435                 switch (col_len) {
4436                 case 3:
4437                     color =
4438                         (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4439                                                          0x011000) |
4440                          ((color & 0x00F)
4441                           * 0x001100)
4442                          | 0x000000FF);
4443                     break;
4444                 case 4:
4445                     color =
4446                         (((color & 0xF000) *
4447                           0x11000) | ((color & 0x0F00) *
4448                                       0x01100) | ((color &
4449                                                    0x00F0) *
4450                                                   0x00110) |
4451                          ((color & 0x000F) * 0x00011)
4452                         );
4453                     break;
4454                 case 6:
4455                     color = (color << 8) + 0xff /* shift left by 8 */ ;
4456                     break;
4457                 case 8:
4458                     break;
4459                 default:
4460                     rrd_set_error("the color format is #RRGGBB[AA]");
4461                     return;
4462                 }
4463                 if ((ci = grc_conv(col_nam)) != -1) {
4464                     im->graph_col[ci] = gfx_hex_to_col(color);
4465                 } else {
4466                     rrd_set_error("invalid color name '%s'", col_nam);
4467                     return;
4468                 }
4469             } else {
4470                 rrd_set_error("invalid color def format");
4471                 return;
4472             }
4473             break;
4474         case 'n':{
4475             char      prop[15];
4476             double    size = 1;
4477             int       end;
4478
4479             old_locale = setlocale(LC_NUMERIC, "C");
4480             if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4481                 int       sindex, propidx;
4482
4483                 setlocale(LC_NUMERIC, old_locale);
4484                 if ((sindex = text_prop_conv(prop)) != -1) {
4485                     for (propidx = sindex;
4486                          propidx < TEXT_PROP_LAST; propidx++) {
4487                         if (size > 0) {
4488                             rrd_set_font_desc(im,propidx,NULL,size);   
4489                         }
4490                         if ((int) strlen(optarg) > end+2) {
4491                             if (optarg[end] == ':') {
4492                                 rrd_set_font_desc(im,propidx,optarg + end + 1,0);   
4493                             } else {
4494                                 rrd_set_error
4495                                     ("expected : after font size in '%s'",
4496                                      optarg);
4497                                 return;
4498                             }
4499                         }
4500                         /* only run the for loop for DEFAULT (0) for
4501                            all others, we break here. woodo programming */
4502                         if (propidx == sindex && sindex != 0)
4503                             break;
4504                     }
4505                 } else {
4506                     rrd_set_error("invalid fonttag '%s'", prop);
4507                     return;
4508                 }
4509             } else {
4510                 setlocale(LC_NUMERIC, old_locale);
4511                 rrd_set_error("invalid text property format");
4512                 return;
4513             }
4514             break;
4515         }
4516         case 'm':
4517             old_locale = setlocale(LC_NUMERIC, "C");
4518             im->zoom = atof(optarg);
4519             setlocale(LC_NUMERIC, old_locale);
4520             if (im->zoom <= 0.0) {
4521                 rrd_set_error("zoom factor must be > 0");
4522                 return;
4523             }
4524             break;
4525         case 't':
4526             strncpy(im->title, optarg, 150);
4527             im->title[150] = '\0';
4528             break;
4529         case 'R':
4530             if (strcmp(optarg, "normal") == 0) {
4531                 cairo_font_options_set_antialias
4532                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4533                 cairo_font_options_set_hint_style
4534                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4535             } else if (strcmp(optarg, "light") == 0) {
4536                 cairo_font_options_set_antialias
4537                     (im->font_options, CAIRO_ANTIALIAS_GRAY);
4538                 cairo_font_options_set_hint_style
4539                     (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4540             } else if (strcmp(optarg, "mono") == 0) {
4541                 cairo_font_options_set_antialias
4542                     (im->font_options, CAIRO_ANTIALIAS_NONE);
4543                 cairo_font_options_set_hint_style
4544                     (im->font_options, CAIRO_HINT_STYLE_FULL);
4545             } else {
4546                 rrd_set_error("unknown font-render-mode '%s'", optarg);
4547                 return;
4548             }
4549             break;
4550         case 'G':
4551             if (strcmp(optarg, "normal") == 0)
4552                 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4553             else if (strcmp(optarg, "mono") == 0)
4554                 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4555             else {
4556                 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4557                 return;
4558             }
4559             break;
4560         case 'B':
4561             /* not supported curently */
4562             break;
4563         case 'W':
4564             strncpy(im->watermark, optarg, 100);
4565             im->watermark[99] = '\0';
4566             break;
4567         case 'd':
4568         {
4569             if (im->daemon_addr != NULL)
4570             {
4571                 rrd_set_error ("You cannot specify --daemon "
4572                         "more than once.");
4573                 return;
4574             }
4575
4576             im->daemon_addr = strdup(optarg);
4577             if (im->daemon_addr == NULL)
4578             {
4579               rrd_set_error("strdup failed");
4580               return;
4581             }
4582
4583             break;
4584         }
4585         case '?':
4586             if (optopt != 0)
4587                 rrd_set_error("unknown option '%c'", optopt);
4588             else
4589                 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4590             return;
4591         }
4592     } /* while (1) */
4593
4594     {   /* try to connect to rrdcached */
4595         int status = rrdc_connect(im->daemon_addr);
4596         if (status != 0) return;
4597     }
4598     
4599     pango_cairo_context_set_font_options(pango_layout_get_context(im->layout), im->font_options);
4600     pango_layout_context_changed(im->layout);
4601
4602
4603
4604     if (im->logarithmic && im->minval <= 0) {
4605         rrd_set_error
4606             ("for a logarithmic yaxis you must specify a lower-limit > 0");
4607         return;
4608     }
4609
4610     if (rrd_proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4611         /* error string is set in rrd_parsetime.c */
4612         return;
4613     }
4614
4615     if (start_tmp < 3600 * 24 * 365 * 10) {
4616         rrd_set_error
4617             ("the first entry to fetch should be after 1980 (%ld)",
4618              start_tmp);
4619         return;
4620     }
4621
4622     if (end_tmp < start_tmp) {
4623         rrd_set_error
4624             ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4625         return;
4626     }
4627
4628     im->start = start_tmp;
4629     im->end = end_tmp;
4630     im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4631 }
4632
4633 int rrd_graph_color(
4634     image_desc_t
4635     *im,
4636     char *var,
4637     char *err,
4638     int optional)
4639 {
4640     char     *color;
4641     graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4642
4643     color = strstr(var, "#");
4644     if (color == NULL) {
4645         if (optional == 0) {
4646             rrd_set_error("Found no color in %s", err);
4647             return 0;
4648         }
4649         return 0;
4650     } else {
4651         int       n = 0;
4652         char     *rest;
4653         long unsigned int col;
4654
4655         rest = strstr(color, ":");
4656         if (rest != NULL)
4657             n = rest - color;
4658         else
4659             n = strlen(color);
4660         switch (n) {
4661         case 7:
4662             sscanf(color, "#%6lx%n", &col, &n);
4663             col = (col << 8) + 0xff /* shift left by 8 */ ;
4664             if (n != 7)
4665                 rrd_set_error("Color problem in %s", err);
4666             break;
4667         case 9:
4668             sscanf(color, "#%8lx%n", &col, &n);
4669             if (n == 9)
4670                 break;
4671         default:
4672             rrd_set_error("Color problem in %s", err);
4673         }
4674         if (rrd_test_error())
4675             return 0;
4676         gdp->col = gfx_hex_to_col(col);
4677         return n;
4678     }
4679 }
4680
4681
4682 int bad_format(
4683     char *fmt)
4684 {
4685     char     *ptr;
4686     int       n = 0;
4687
4688     ptr = fmt;
4689     while (*ptr != '\0')
4690         if (*ptr++ == '%') {
4691
4692             /* line cannot end with percent char */
4693             if (*ptr == '\0')
4694                 return 1;
4695             /* '%s', '%S' and '%%' are allowed */
4696             if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4697                 ptr++;
4698             /* %c is allowed (but use only with vdef!) */
4699             else if (*ptr == 'c') {
4700                 ptr++;
4701                 n = 1;
4702             }
4703
4704             /* or else '% 6.2lf' and such are allowed */
4705             else {
4706                 /* optional padding character */
4707                 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4708                     ptr++;
4709                 /* This should take care of 'm.n' with all three optional */
4710                 while (*ptr >= '0' && *ptr <= '9')
4711                     ptr++;
4712                 if (*ptr == '.')
4713                     ptr++;
4714                 while (*ptr >= '0' && *ptr <= '9')
4715                     ptr++;
4716                 /* Either 'le', 'lf' or 'lg' must follow here */
4717                 if (*ptr++ != 'l')
4718                     return 1;
4719                 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4720                     ptr++;
4721                 else
4722                     return 1;
4723                 n++;
4724             }
4725         }
4726
4727     return (n != 1);
4728 }
4729
4730
4731 int vdef_parse(
4732     struct graph_desc_t
4733     *gdes,
4734     const char *const str)
4735 {
4736     /* A VDEF currently is either "func" or "param,func"
4737      * so the parsing is rather simple.  Change if needed.
4738      */
4739     double    param;
4740     char      func[30];
4741     int       n;
4742     char     *old_locale;
4743
4744     n = 0;
4745     old_locale = setlocale(LC_NUMERIC, "C");
4746     sscanf(str, "%le,%29[A-Z]%n", &param, func, &n);
4747     setlocale(LC_NUMERIC, old_locale);
4748     if (n == (int) strlen(str)) {   /* matched */
4749         ;
4750     } else {
4751         n = 0;
4752         sscanf(str, "%29[A-Z]%n", func, &n);
4753         if (n == (int) strlen(str)) {   /* matched */
4754             param = DNAN;
4755         } else {
4756             rrd_set_error
4757                 ("Unknown function string '%s' in VDEF '%s'",
4758                  str, gdes->vname);
4759             return -1;
4760         }
4761     }
4762     if (!strcmp("PERCENT", func))
4763         gdes->vf.op = VDEF_PERCENT;
4764     else if (!strcmp("PERCENTNAN", func))
4765         gdes->vf.op = VDEF_PERCENTNAN;
4766     else if (!strcmp("MAXIMUM", func))
4767         gdes->vf.op = VDEF_MAXIMUM;
4768     else if (!strcmp("AVERAGE", func))
4769         gdes->vf.op = VDEF_AVERAGE;
4770     else if (!strcmp("STDEV", func))
4771         gdes->vf.op = VDEF_STDEV;
4772     else if (!strcmp("MINIMUM", func))
4773         gdes->vf.op = VDEF_MINIMUM;
4774     else if (!strcmp("TOTAL", func))
4775         gdes->vf.op = VDEF_TOTAL;
4776     else if (!strcmp("FIRST", func))
4777         gdes->vf.op = VDEF_FIRST;
4778     else if (!strcmp("LAST", func))
4779         gdes->vf.op = VDEF_LAST;
4780     else if (!strcmp("LSLSLOPE", func))
4781         gdes->vf.op = VDEF_LSLSLOPE;
4782     else if (!strcmp("LSLINT", func))
4783         gdes->vf.op = VDEF_LSLINT;
4784     else if (!strcmp("LSLCORREL", func))
4785         gdes->vf.op = VDEF_LSLCORREL;
4786     else {
4787         rrd_set_error
4788             ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4789         return -1;
4790     };
4791     switch (gdes->vf.op) {
4792     case VDEF_PERCENT:
4793     case VDEF_PERCENTNAN:
4794         if (isnan(param)) { /* no parameter given */
4795             rrd_set_error
4796                 ("Function '%s' needs parameter in VDEF '%s'\n",
4797                  func, gdes->vname);
4798             return -1;
4799         };
4800         if (param >= 0.0 && param <= 100.0) {
4801             gdes->vf.param = param;
4802             gdes->vf.val = DNAN;    /* undefined */
4803             gdes->vf.when = 0;  /* undefined */
4804         } else {
4805             rrd_set_error
4806                 ("Parameter '%f' out of range in VDEF '%s'\n",
4807                  param, gdes->vname);
4808             return -1;
4809         };
4810         break;
4811     case VDEF_MAXIMUM:
4812     case VDEF_AVERAGE:
4813     case VDEF_STDEV:
4814     case VDEF_MINIMUM:
4815     case VDEF_TOTAL:
4816     case VDEF_FIRST:
4817     case VDEF_LAST:
4818     case VDEF_LSLSLOPE:
4819     case VDEF_LSLINT:
4820     case VDEF_LSLCORREL:
4821         if (isnan(param)) {
4822             gdes->vf.param = DNAN;
4823             gdes->vf.val = DNAN;
4824             gdes->vf.when = 0;
4825         } else {
4826             rrd_set_error
4827                 ("Function '%s' needs no parameter in VDEF '%s'\n",
4828                  func, gdes->vname);
4829             return -1;
4830         };
4831         break;
4832     };
4833     return 0;
4834 }
4835
4836
4837 int vdef_calc(
4838     image_desc_t *im,
4839     int gdi)
4840 {
4841     graph_desc_t *src, *dst;
4842     rrd_value_t *data;
4843     long      step, steps;
4844
4845     dst = &im->gdes[gdi];
4846     src = &im->gdes[dst->vidx];
4847     data = src->data + src->ds;
4848
4849     steps = (src->end - src->start) / src->step;
4850 #if 0
4851     printf
4852         ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4853          src->start, src->end, steps);
4854 #endif
4855     switch (dst->vf.op) {
4856     case VDEF_PERCENT:{
4857         rrd_value_t *array;
4858         int       field;
4859         if ((array = (rrd_value_t*)malloc(steps * sizeof(double))) == NULL) {
4860             rrd_set_error("malloc VDEV_PERCENT");
4861             return -1;
4862         }
4863         for (step = 0; step < steps; step++) {
4864             array[step] = data[step * src->ds_cnt];
4865         }
4866         qsort(array, step, sizeof(double), vdef_percent_compar);
4867         field = (steps - 1) * dst->vf.param / 100;
4868         dst->vf.val = array[field];
4869         dst->vf.when = 0;   /* no time component */
4870         free(array);
4871 #if 0
4872         for (step = 0; step < steps; step++)
4873             printf("DEBUG: %3li:%10.2f %c\n",
4874                    step, array[step], step == field ? '*' : ' ');
4875 #endif
4876     }
4877         break;
4878     case VDEF_PERCENTNAN:{
4879         rrd_value_t *array;
4880         int       field;
4881        /* count number of "valid" values */
4882        int nancount=0;
4883        for (step = 0; step < steps; step++) {
4884          if (!isnan(data[step * src->ds_cnt])) { nancount++; }
4885        }
4886        /* and allocate it */
4887         if ((array = (rrd_value_t*)malloc(nancount * sizeof(double))) == NULL) {
4888             rrd_set_error("malloc VDEV_PERCENT");
4889             return -1;
4890         }
4891        /* and fill it in */
4892        field=0;
4893         for (step = 0; step < steps; step++) {
4894            if (!isnan(data[step * src->ds_cnt])) {
4895                 array[field] = data[step * src->ds_cnt];
4896                field++;
4897             }
4898         }
4899         qsort(array, nancount, sizeof(double), vdef_percent_compar);
4900         field = (nancount - 1) * dst->vf.param / 100;
4901         dst->vf.val = array[field];
4902         dst->vf.when = 0;   /* no time component */
4903         free(array);
4904     }
4905         break;
4906     case VDEF_MAXIMUM:
4907         step = 0;
4908         while (step != steps && isnan(data[step * src->ds_cnt]))
4909             step++;
4910         if (step == steps) {
4911             dst->vf.val = DNAN;
4912             dst->vf.when = 0;
4913         } else {
4914             dst->vf.val = data[step * src->ds_cnt];
4915             dst->vf.when = src->start + (step + 1) * src->step;
4916         }
4917         while (step != steps) {
4918             if (finite(data[step * src->ds_cnt])) {
4919                 if (data[step * src->ds_cnt] > dst->vf.val) {
4920                     dst->vf.val = data[step * src->ds_cnt];
4921                     dst->vf.when = src->start + (step + 1) * src->step;
4922                 }
4923             }
4924             step++;
4925         }
4926         break;
4927     case VDEF_TOTAL:
4928     case VDEF_STDEV:
4929     case VDEF_AVERAGE:{
4930         int       cnt = 0;
4931         double    sum = 0.0;
4932         double    average = 0.0;
4933
4934         for (step = 0; step < steps; step++) {
4935             if (finite(data[step * src->ds_cnt])) {
4936                 sum += data[step * src->ds_cnt];
4937                 cnt++;
4938             };
4939         }
4940         if (cnt) {
4941             if (dst->vf.op == VDEF_TOTAL) {
4942                 dst->vf.val = sum * src->step;
4943                 dst->vf.when = 0;   /* no time component */
4944             } else if (dst->vf.op == VDEF_AVERAGE) {
4945                 dst->vf.val = sum / cnt;
4946                 dst->vf.when = 0;   /* no time component */
4947             } else {
4948                 average = sum / cnt;
4949                 sum = 0.0;
4950                 for (step = 0; step < steps; step++) {
4951                     if (finite(data[step * src->ds_cnt])) {
4952                         sum += pow((data[step * src->ds_cnt] - average), 2.0);
4953                     };
4954                 }
4955                 dst->vf.val = pow(sum / cnt, 0.5);
4956                 dst->vf.when = 0;   /* no time component */
4957             };
4958         } else {
4959             dst->vf.val = DNAN;
4960             dst->vf.when = 0;
4961         }
4962     }
4963         break;
4964     case VDEF_MINIMUM:
4965         step = 0;
4966         while (step != steps && isnan(data[step * src->ds_cnt]))
4967             step++;
4968         if (step == steps) {
4969             dst->vf.val = DNAN;
4970             dst->vf.when = 0;
4971         } else {
4972             dst->vf.val = data[step * src->ds_cnt];
4973             dst->vf.when = src->start + (step + 1) * src->step;
4974         }
4975         while (step != steps) {
4976             if (finite(data[step * src->ds_cnt])) {
4977                 if (data[step * src->ds_cnt] < dst->vf.val) {
4978                     dst->vf.val = data[step * src->ds_cnt];
4979                     dst->vf.when = src->start + (step + 1) * src->step;
4980                 }
4981             }
4982             step++;
4983         }
4984         break;
4985     case VDEF_FIRST:
4986         /* The time value returned here is one step before the
4987          * actual time value.  This is the start of the first
4988          * non-NaN interval.
4989          */
4990         step = 0;
4991         while (step != steps && isnan(data[step * src->ds_cnt]))
4992             step++;
4993         if (step == steps) {    /* all entries were NaN */
4994             dst->vf.val = DNAN;
4995             dst->vf.when = 0;
4996         } else {
4997             dst->vf.val = data[step * src->ds_cnt];
4998             dst->vf.when = src->start + step * src->step;
4999         }
5000         break;
5001     case VDEF_LAST:
5002         /* The time value returned here is the
5003          * actual time value.  This is the end of the last
5004          * non-NaN interval.
5005          */
5006         step = steps - 1;
5007         while (step >= 0 && isnan(data[step * src->ds_cnt]))
5008             step--;
5009         if (step < 0) { /* all entries were NaN */
5010             dst->vf.val = DNAN;
5011             dst->vf.when = 0;
5012         } else {
5013             dst->vf.val = data[step * src->ds_cnt];
5014             dst->vf.when = src->start + (step + 1) * src->step;
5015         }
5016         break;
5017     case VDEF_LSLSLOPE:
5018     case VDEF_LSLINT:
5019     case VDEF_LSLCORREL:{
5020         /* Bestfit line by linear least squares method */
5021
5022         int       cnt = 0;
5023         double    SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
5024
5025         SUMx = 0;
5026         SUMy = 0;
5027         SUMxy = 0;
5028         SUMxx = 0;
5029         SUMyy = 0;
5030         for (step = 0; step < steps; step++) {
5031             if (finite(data[step * src->ds_cnt])) {
5032                 cnt++;
5033                 SUMx += step;
5034                 SUMxx += step * step;
5035                 SUMxy += step * data[step * src->ds_cnt];
5036                 SUMy += data[step * src->ds_cnt];
5037                 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
5038             };
5039         }
5040
5041         slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
5042         y_intercept = (SUMy - slope * SUMx) / cnt;
5043         correl =
5044             (SUMxy -
5045              (SUMx * SUMy) / cnt) /
5046             sqrt((SUMxx -
5047                   (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
5048         if (cnt) {
5049             if (dst->vf.op == VDEF_LSLSLOPE) {
5050                 dst->vf.val = slope;
5051                 dst->vf.when = 0;
5052             } else if (dst->vf.op == VDEF_LSLINT) {
5053                 dst->vf.val = y_intercept;
5054                 dst->vf.when = 0;
5055             } else if (dst->vf.op == VDEF_LSLCORREL) {
5056                 dst->vf.val = correl;
5057                 dst->vf.when = 0;
5058             };
5059         } else {
5060             dst->vf.val = DNAN;
5061             dst->vf.when = 0;
5062         }
5063     }
5064         break;
5065     }
5066     return 0;
5067 }
5068
5069 /* NaN < -INF < finite_values < INF */
5070 int vdef_percent_compar(
5071     const void
5072     *a,
5073     const void
5074     *b)
5075 {
5076     /* Equality is not returned; this doesn't hurt except
5077      * (maybe) for a little performance.
5078      */
5079
5080     /* First catch NaN values. They are smallest */
5081     if (isnan(*(double *) a))
5082         return -1;
5083     if (isnan(*(double *) b))
5084         return 1;
5085     /* NaN doesn't reach this part so INF and -INF are extremes.
5086      * The sign from isinf() is compatible with the sign we return
5087      */
5088     if (isinf(*(double *) a))
5089         return isinf(*(double *) a);
5090     if (isinf(*(double *) b))
5091         return isinf(*(double *) b);
5092     /* If we reach this, both values must be finite */
5093     if (*(double *) a < *(double *) b)
5094         return -1;
5095     else
5096         return 1;
5097 }
5098
5099 void grinfo_push(
5100     image_desc_t *im,
5101     char *key,
5102     rrd_info_type_t type,
5103     rrd_infoval_t value)
5104 {
5105     im->grinfo_current = rrd_info_push(im->grinfo_current, key, type, value);
5106     if (im->grinfo == NULL) {
5107         im->grinfo = im->grinfo_current;
5108     }
5109 }