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