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