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