a93fbb1c52708ca3d047630eb587434c6ac29a00
[rrdtool.git] / src / rrd_graph.c
1 /****************************************************************************
2  * RRDtool 1.4.2  Copyright by Tobi Oetiker, 1997-2009
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 #include "rrd_graph.h"
33 #include "rrd_client.h"
34
35 /* some constant definitions */
36
37
38
39 #ifndef RRD_DEFAULT_FONT
40 /* there is special code later to pick Cour.ttf when running on windows */
41 #define RRD_DEFAULT_FONT "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
42 #endif
43
44 text_prop_t text_prop[] = {
45     {8.0, RRD_DEFAULT_FONT,NULL}
46     ,                   /* default */
47     {9.0, RRD_DEFAULT_FONT,NULL}
48     ,                   /* title */
49     {7.0, RRD_DEFAULT_FONT,NULL}
50     ,                   /* axis */
51     {8.0, RRD_DEFAULT_FONT,NULL}
52     ,                   /* unit */
53     {8.0, RRD_DEFAULT_FONT,NULL} /* legend */
54     ,
55     {5.5, RRD_DEFAULT_FONT,NULL} /* watermark */
56 };
57
58 xlab_t    xlab[] = {
59     {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
60     ,
61     {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
62     ,
63     {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
64     ,
65     {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
66     ,
67     {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
68     ,
69     {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
70     ,
71     {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 6, 0, "%a %H:%M"}
72     ,
73     {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
74     ,
75     {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
76     ,
77     /*{300,             0,   TMT_HOUR,3,    TMT_HOUR,12,   TMT_HOUR,12,    12*3600,"%a %p"},  this looks silly */
78     {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
79     ,
80     {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
81     ,
82     {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
83     ,
84     {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
85     ,
86     {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
87     ,
88     {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
89      "Week %V"}
90     ,
91     {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
92      "%b"}
93     ,
94     {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
95      "%b"}
96     ,
97     {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
98     ,
99     {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
100      365 * 24 * 3600, "%y"}
101     ,
102     {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
103 };
104
105 /* sensible y label intervals ...*/
106
107 ylab_t    ylab[] = {
108     {0.1, {1, 2, 5, 10}
109      }
110     ,
111     {0.2, {1, 5, 10, 20}
112      }
113     ,
114     {0.5, {1, 2, 4, 10}
115      }
116     ,
117     {1.0, {1, 2, 5, 10}
118      }
119     ,
120     {2.0, {1, 5, 10, 20}
121      }
122     ,
123     {5.0, {1, 2, 4, 10}
124      }
125     ,
126     {10.0, {1, 2, 5, 10}
127      }
128     ,
129     {20.0, {1, 5, 10, 20}
130      }
131     ,
132     {50.0, {1, 2, 4, 10}
133      }
134     ,
135     {100.0, {1, 2, 5, 10}
136      }
137     ,
138     {200.0, {1, 5, 10, 20}
139      }
140     ,
141     {500.0, {1, 2, 4, 10}
142      }
143     ,
144     {0.0, {0, 0, 0, 0}
145      }
146 };
147
148
149 gfx_color_t graph_col[] =   /* default colors */
150 {
151     {1.00, 1.00, 1.00, 1.00},   /* canvas     */
152     {0.95, 0.95, 0.95, 1.00},   /* background */
153     {0.81, 0.81, 0.81, 1.00},   /* shade A    */
154     {0.62, 0.62, 0.62, 1.00},   /* shade B    */
155     {0.56, 0.56, 0.56, 0.75},   /* grid       */
156     {0.87, 0.31, 0.31, 0.60},   /* major grid */
157     {0.00, 0.00, 0.00, 1.00},   /* font       */
158     {0.50, 0.12, 0.12, 1.00},   /* arrow      */
159     {0.12, 0.12, 0.12, 1.00},   /* axis       */
160     {0.00, 0.00, 0.00, 1.00}    /* frame      */
161 };
162
163
164 /* #define DEBUG */
165
166 #ifdef DEBUG
167 # define DPRINT(x)    (void)(printf x, printf("\n"))
168 #else
169 # define DPRINT(x)
170 #endif
171
172
173 /* initialize with xtr(im,0); */
174 int xtr(
175     image_desc_t *im,
176     time_t mytime)
177 {
178     static double pixie;
179
180     if (mytime == 0) {
181         pixie = (double) im->xsize / (double) (im->end - im->start);
182         return im->xorigin;
183     }
184     return (int) ((double) im->xorigin + pixie * (mytime - im->start));
185 }
186
187 /* translate data values into y coordinates */
188 double ytr(
189     image_desc_t *im,
190     double value)
191 {
192     static double pixie;
193     double    yval;
194
195     if (isnan(value)) {
196         if (!im->logarithmic)
197             pixie = (double) im->ysize / (im->maxval - im->minval);
198         else
199             pixie =
200                 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
201         yval = im->yorigin;
202     } else if (!im->logarithmic) {
203         yval = im->yorigin - pixie * (value - im->minval);
204     } else {
205         if (value < im->minval) {
206             yval = im->yorigin;
207         } else {
208             yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
209         }
210     }
211     return yval;
212 }
213
214
215
216 /* conversion function for symbolic entry names */
217
218
219 #define conv_if(VV,VVV) \
220    if (strcmp(#VV, string) == 0) return VVV ;
221
222 enum gf_en gf_conv(
223     char *string)
224 {
225
226     conv_if(PRINT, GF_PRINT);
227     conv_if(GPRINT, GF_GPRINT);
228     conv_if(COMMENT, GF_COMMENT);
229     conv_if(HRULE, GF_HRULE);
230     conv_if(VRULE, GF_VRULE);
231     conv_if(LINE, GF_LINE);
232     conv_if(AREA, GF_AREA);
233     conv_if(STACK, GF_STACK);
234     conv_if(TICK, GF_TICK);
235     conv_if(TEXTALIGN, GF_TEXTALIGN);
236     conv_if(DEF, GF_DEF);
237     conv_if(CDEF, GF_CDEF);
238     conv_if(VDEF, GF_VDEF);
239     conv_if(XPORT, GF_XPORT);
240     conv_if(SHIFT, GF_SHIFT);
241
242     return (enum gf_en)(-1);
243 }
244
245 enum gfx_if_en if_conv(
246     char *string)
247 {
248
249     conv_if(PNG, IF_PNG);
250     conv_if(SVG, IF_SVG);
251     conv_if(EPS, IF_EPS);
252     conv_if(PDF, IF_PDF);
253
254     return (enum gfx_if_en)(-1);
255 }
256
257 enum tmt_en tmt_conv(
258     char *string)
259 {
260
261     conv_if(SECOND, TMT_SECOND);
262     conv_if(MINUTE, TMT_MINUTE);
263     conv_if(HOUR, TMT_HOUR);
264     conv_if(DAY, TMT_DAY);
265     conv_if(WEEK, TMT_WEEK);
266     conv_if(MONTH, TMT_MONTH);
267     conv_if(YEAR, TMT_YEAR);
268     return (enum tmt_en)(-1);
269 }
270
271 enum grc_en grc_conv(
272     char *string)
273 {
274
275     conv_if(BACK, GRC_BACK);
276     conv_if(CANVAS, GRC_CANVAS);
277     conv_if(SHADEA, GRC_SHADEA);
278     conv_if(SHADEB, GRC_SHADEB);
279     conv_if(GRID, GRC_GRID);
280     conv_if(MGRID, GRC_MGRID);
281     conv_if(FONT, GRC_FONT);
282     conv_if(ARROW, GRC_ARROW);
283     conv_if(AXIS, GRC_AXIS);
284     conv_if(FRAME, GRC_FRAME);
285
286     return (enum grc_en)(-1);
287 }
288
289 enum text_prop_en text_prop_conv(
290     char *string)
291 {
292
293     conv_if(DEFAULT, TEXT_PROP_DEFAULT);
294     conv_if(TITLE, TEXT_PROP_TITLE);
295     conv_if(AXIS, TEXT_PROP_AXIS);
296     conv_if(UNIT, TEXT_PROP_UNIT);
297     conv_if(LEGEND, TEXT_PROP_LEGEND);
298     conv_if(WATERMARK, TEXT_PROP_WATERMARK);
299     return (enum text_prop_en)(-1);
300 }
301
302
303 #undef conv_if
304
305 int im_free(
306     image_desc_t *im)
307 {
308     unsigned long i, ii;
309     cairo_status_t status = (cairo_status_t) 0;
310
311     if (im == NULL)
312         return 0;
313
314     if (im->daemon_addr != NULL)
315       free(im->daemon_addr);
316
317     for (i = 0; i < (unsigned) im->gdes_c; i++) {
318         if (im->gdes[i].data_first) {
319             /* careful here, because a single pointer can occur several times */
320             free(im->gdes[i].data);
321             if (im->gdes[i].ds_namv) {
322                 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
323                     free(im->gdes[i].ds_namv[ii]);
324                 free(im->gdes[i].ds_namv);
325             }
326         }
327         /* free allocated memory used for dashed lines */
328         if (im->gdes[i].p_dashes != NULL)
329             free(im->gdes[i].p_dashes);
330
331         free(im->gdes[i].p_data);
332         free(im->gdes[i].rpnp);
333     }
334     free(im->gdes);
335     if (im->font_options)
336         cairo_font_options_destroy(im->font_options);
337
338     if (im->cr) {
339         status = cairo_status(im->cr);
340         cairo_destroy(im->cr);
341     }
342     if (im->rendered_image) {
343         free(im->rendered_image);
344     }
345
346     if (im->layout) {
347         g_object_unref (im->layout);
348     }
349
350     if (im->surface)
351         cairo_surface_destroy(im->surface);
352
353     if (status)
354         fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
355                 cairo_status_to_string(status));
356
357     return 0;
358 }
359
360 /* find SI magnitude symbol for the given number*/
361 void auto_scale(
362     image_desc_t *im,   /* image description */
363     double *value,
364     char **symb_ptr,
365     double *magfact)
366 {
367
368     char     *symbol[] = { "a", /* 10e-18 Atto */
369         "f",            /* 10e-15 Femto */
370         "p",            /* 10e-12 Pico */
371         "n",            /* 10e-9  Nano */
372         "u",            /* 10e-6  Micro */
373         "m",            /* 10e-3  Milli */
374         " ",            /* Base */
375         "k",            /* 10e3   Kilo */
376         "M",            /* 10e6   Mega */
377         "G",            /* 10e9   Giga */
378         "T",            /* 10e12  Tera */
379         "P",            /* 10e15  Peta */
380         "E"
381     };                  /* 10e18  Exa */
382
383     int       symbcenter = 6;
384     int       sindex;
385
386     if (*value == 0.0 || isnan(*value)) {
387         sindex = 0;
388         *magfact = 1.0;
389     } else {
390         sindex = floor(log(fabs(*value)) / log((double) im->base));
391         *magfact = pow((double) im->base, (double) sindex);
392         (*value) /= (*magfact);
393     }
394     if (sindex <= symbcenter && sindex >= -symbcenter) {
395         (*symb_ptr) = symbol[sindex + symbcenter];
396     } else {
397         (*symb_ptr) = "?";
398     }
399 }
400
401
402 static char si_symbol[] = {
403     'a',                /* 10e-18 Atto */
404     'f',                /* 10e-15 Femto */
405     'p',                /* 10e-12 Pico */
406     'n',                /* 10e-9  Nano */
407     'u',                /* 10e-6  Micro */
408     'm',                /* 10e-3  Milli */
409     ' ',                /* Base */
410     'k',                /* 10e3   Kilo */
411     'M',                /* 10e6   Mega */
412     'G',                /* 10e9   Giga */
413     'T',                /* 10e12  Tera */
414     'P',                /* 10e15  Peta */
415     'E',                /* 10e18  Exa */
416 };
417 static const int si_symbcenter = 6;
418
419 /* find SI magnitude symbol for the numbers on the y-axis*/
420 void si_unit(
421     image_desc_t *im    /* image description */
422     )
423 {
424
425     double    digits, viewdigits = 0;
426
427     digits =
428         floor(log(max(fabs(im->minval), fabs(im->maxval))) /
429               log((double) im->base));
430
431     if (im->unitsexponent != 9999) {
432         /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
433         viewdigits = floor((double)(im->unitsexponent / 3));
434     } else {
435         viewdigits = digits;
436     }
437
438     im->magfact = pow((double) im->base, digits);
439
440 #ifdef DEBUG
441     printf("digits %6.3f  im->magfact %6.3f\n", digits, im->magfact);
442 #endif
443
444     im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
445
446     if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
447         ((viewdigits + si_symbcenter) >= 0))
448         im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
449     else
450         im->symbol = '?';
451 }
452
453 /*  move min and max values around to become sensible */
454
455 void expand_range(
456     image_desc_t *im)
457 {
458     double    sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
459         600.0, 500.0, 400.0, 300.0, 250.0,
460         200.0, 125.0, 100.0, 90.0, 80.0,
461         75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
462         25.0, 20.0, 10.0, 9.0, 8.0,
463         7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
464         2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
465         0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
466     };
467
468     double    scaled_min, scaled_max;
469     double    adj;
470     int       i;
471
472
473
474 #ifdef DEBUG
475     printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
476            im->minval, im->maxval, im->magfact);
477 #endif
478
479     if (isnan(im->ygridstep)) {
480         if (im->extra_flags & ALTAUTOSCALE) {
481             /* measure the amplitude of the function. Make sure that
482                graph boundaries are slightly higher then max/min vals
483                so we can see amplitude on the graph */
484             double    delt, fact;
485
486             delt = im->maxval - im->minval;
487             adj = delt * 0.1;
488             fact = 2.0 * pow(10.0,
489                              floor(log10
490                                    (max(fabs(im->minval), fabs(im->maxval)) /
491                                     im->magfact)) - 2);
492             if (delt < fact) {
493                 adj = (fact - delt) * 0.55;
494 #ifdef DEBUG
495                 printf
496                     ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
497                      im->minval, im->maxval, delt, fact, adj);
498 #endif
499             }
500             im->minval -= adj;
501             im->maxval += adj;
502         } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
503             /* measure the amplitude of the function. Make sure that
504                graph boundaries are slightly lower than min vals
505                so we can see amplitude on the graph */
506             adj = (im->maxval - im->minval) * 0.1;
507             im->minval -= adj;
508         } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
509             /* measure the amplitude of the function. Make sure that
510                graph boundaries are slightly higher than max vals
511                so we can see amplitude on the graph */
512             adj = (im->maxval - im->minval) * 0.1;
513             im->maxval += adj;
514         } else {
515             scaled_min = im->minval / im->magfact;
516             scaled_max = im->maxval / im->magfact;
517
518             for (i = 1; sensiblevalues[i] > 0; i++) {
519                 if (sensiblevalues[i - 1] >= scaled_min &&
520                     sensiblevalues[i] <= scaled_min)
521                     im->minval = sensiblevalues[i] * (im->magfact);
522
523                 if (-sensiblevalues[i - 1] <= scaled_min &&
524                     -sensiblevalues[i] >= scaled_min)
525                     im->minval = -sensiblevalues[i - 1] * (im->magfact);
526
527                 if (sensiblevalues[i - 1] >= scaled_max &&
528                     sensiblevalues[i] <= scaled_max)
529                     im->maxval = sensiblevalues[i - 1] * (im->magfact);
530
531                 if (-sensiblevalues[i - 1] <= scaled_max &&
532                     -sensiblevalues[i] >= scaled_max)
533                     im->maxval = -sensiblevalues[i] * (im->magfact);
534             }
535         }
536     } else {
537         /* adjust min and max to the grid definition if there is one */
538         im->minval = (double) im->ylabfact * im->ygridstep *
539             floor(im->minval / ((double) im->ylabfact * im->ygridstep));
540         im->maxval = (double) im->ylabfact * im->ygridstep *
541             ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
542     }
543
544 #ifdef DEBUG
545     fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
546             im->minval, im->maxval, im->magfact);
547 #endif
548 }
549
550
551 void apply_gridfit(
552     image_desc_t *im)
553 {
554     if (isnan(im->minval) || isnan(im->maxval))
555         return;
556     ytr(im, DNAN);
557     if (im->logarithmic) {
558         double    ya, yb, ypix, ypixfrac;
559         double    log10_range = log10(im->maxval) - log10(im->minval);
560
561         ya = pow((double) 10, floor(log10(im->minval)));
562         while (ya < im->minval)
563             ya *= 10;
564         if (ya > im->maxval)
565             return;     /* don't have y=10^x gridline */
566         yb = ya * 10;
567         if (yb <= im->maxval) {
568             /* we have at least 2 y=10^x gridlines.
569                Make sure distance between them in pixels
570                are an integer by expanding im->maxval */
571             double    y_pixel_delta = ytr(im, ya) - ytr(im, yb);
572             double    factor = y_pixel_delta / floor(y_pixel_delta);
573             double    new_log10_range = factor * log10_range;
574             double    new_ymax_log10 = log10(im->minval) + new_log10_range;
575
576             im->maxval = pow(10, new_ymax_log10);
577             ytr(im, DNAN);  /* reset precalc */
578             log10_range = log10(im->maxval) - log10(im->minval);
579         }
580         /* make sure first y=10^x gridline is located on
581            integer pixel position by moving scale slightly
582            downwards (sub-pixel movement) */
583         ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
584         ypixfrac = ypix - floor(ypix);
585         if (ypixfrac > 0 && ypixfrac < 1) {
586             double    yfrac = ypixfrac / im->ysize;
587
588             im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
589             im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
590             ytr(im, DNAN);  /* reset precalc */
591         }
592     } else {
593         /* Make sure we have an integer pixel distance between
594            each minor gridline */
595         double    ypos1 = ytr(im, im->minval);
596         double    ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
597         double    y_pixel_delta = ypos1 - ypos2;
598         double    factor = y_pixel_delta / floor(y_pixel_delta);
599         double    new_range = factor * (im->maxval - im->minval);
600         double    gridstep = im->ygrid_scale.gridstep;
601         double    minor_y, minor_y_px, minor_y_px_frac;
602
603         if (im->maxval > 0.0)
604             im->maxval = im->minval + new_range;
605         else
606             im->minval = im->maxval - new_range;
607         ytr(im, DNAN);  /* reset precalc */
608         /* make sure first minor gridline is on integer pixel y coord */
609         minor_y = gridstep * floor(im->minval / gridstep);
610         while (minor_y < im->minval)
611             minor_y += gridstep;
612         minor_y_px = ytr(im, minor_y) + im->ysize;  /* ensure > 0 by adding ysize */
613         minor_y_px_frac = minor_y_px - floor(minor_y_px);
614         if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
615             double    yfrac = minor_y_px_frac / im->ysize;
616             double    range = im->maxval - im->minval;
617
618             im->minval = im->minval - yfrac * range;
619             im->maxval = im->maxval - yfrac * range;
620             ytr(im, DNAN);  /* reset precalc */
621         }
622         calc_horizontal_grid(im);   /* recalc with changed im->maxval */
623     }
624 }
625
626 /* reduce data reimplementation by Alex */
627
628 void reduce_data(
629     enum cf_en cf,      /* which consolidation function ? */
630     unsigned long cur_step, /* step the data currently is in */
631     time_t *start,      /* start, end and step as requested ... */
632     time_t *end,        /* ... by the application will be   ... */
633     unsigned long *step,    /* ... adjusted to represent reality    */
634     unsigned long *ds_cnt,  /* number of data sources in file */
635     rrd_value_t **data)
636 {                       /* two dimensional array containing the data */
637     int       i, reduce_factor = ceil((double) (*step) / (double) cur_step);
638     unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
639         0;
640     rrd_value_t *srcptr, *dstptr;
641
642     (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
643     dstptr = *data;
644     srcptr = *data;
645     row_cnt = ((*end) - (*start)) / cur_step;
646
647 #ifdef DEBUG
648 #define DEBUG_REDUCE
649 #endif
650 #ifdef DEBUG_REDUCE
651     printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
652            row_cnt, reduce_factor, *start, *end, cur_step);
653     for (col = 0; col < row_cnt; col++) {
654         printf("time %10lu: ", *start + (col + 1) * cur_step);
655         for (i = 0; i < *ds_cnt; i++)
656             printf(" %8.2e", srcptr[*ds_cnt * col + i]);
657         printf("\n");
658     }
659 #endif
660
661     /* We have to combine [reduce_factor] rows of the source
662      ** into one row for the destination.  Doing this we also
663      ** need to take care to combine the correct rows.  First
664      ** alter the start and end time so that they are multiples
665      ** of the new step time.  We cannot reduce the amount of
666      ** time so we have to move the end towards the future and
667      ** the start towards the past.
668      */
669     end_offset = (*end) % (*step);
670     start_offset = (*start) % (*step);
671
672     /* If there is a start offset (which cannot be more than
673      ** one destination row), skip the appropriate number of
674      ** source rows and one destination row.  The appropriate
675      ** number is what we do know (start_offset/cur_step) of
676      ** the new interval (*step/cur_step aka reduce_factor).
677      */
678 #ifdef DEBUG_REDUCE
679     printf("start_offset: %lu  end_offset: %lu\n", start_offset, end_offset);
680     printf("row_cnt before:  %lu\n", row_cnt);
681 #endif
682     if (start_offset) {
683         (*start) = (*start) - start_offset;
684         skiprows = reduce_factor - start_offset / cur_step;
685         srcptr += skiprows * *ds_cnt;
686         for (col = 0; col < (*ds_cnt); col++)
687             *dstptr++ = DNAN;
688         row_cnt -= skiprows;
689     }
690 #ifdef DEBUG_REDUCE
691     printf("row_cnt between: %lu\n", row_cnt);
692 #endif
693
694     /* At the end we have some rows that are not going to be
695      ** used, the amount is end_offset/cur_step
696      */
697     if (end_offset) {
698         (*end) = (*end) - end_offset + (*step);
699         skiprows = end_offset / cur_step;
700         row_cnt -= skiprows;
701     }
702 #ifdef DEBUG_REDUCE
703     printf("row_cnt after:   %lu\n", row_cnt);
704 #endif
705
706 /* Sanity check: row_cnt should be multiple of reduce_factor */
707 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
708
709     if (row_cnt % reduce_factor) {
710         printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
711                row_cnt, reduce_factor);
712         printf("BUG in reduce_data()\n");
713         exit(1);
714     }
715
716     /* Now combine reduce_factor intervals at a time
717      ** into one interval for the destination.
718      */
719
720     for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
721         for (col = 0; col < (*ds_cnt); col++) {
722             rrd_value_t newval = DNAN;
723             unsigned long validval = 0;
724
725             for (i = 0; i < reduce_factor; i++) {
726                 if (isnan(srcptr[i * (*ds_cnt) + col])) {
727                     continue;
728                 }
729                 validval++;
730                 if (isnan(newval))
731                     newval = srcptr[i * (*ds_cnt) + col];
732                 else {
733                     switch (cf) {
734                     case CF_HWPREDICT:
735                     case CF_MHWPREDICT:
736                     case CF_DEVSEASONAL:
737                     case CF_DEVPREDICT:
738                     case CF_SEASONAL:
739                     case CF_AVERAGE:
740                         newval += srcptr[i * (*ds_cnt) + col];
741                         break;
742                     case CF_MINIMUM:
743                         newval = min(newval, srcptr[i * (*ds_cnt) + col]);
744                         break;
745                     case CF_FAILURES:
746                         /* an interval contains a failure if any subintervals contained a failure */
747                     case CF_MAXIMUM:
748                         newval = max(newval, srcptr[i * (*ds_cnt) + col]);
749                         break;
750                     case CF_LAST:
751                         newval = srcptr[i * (*ds_cnt) + col];
752                         break;
753                     }
754                 }
755             }
756             if (validval == 0) {
757                 newval = DNAN;
758             } else {
759                 switch (cf) {
760                 case CF_HWPREDICT:
761                 case CF_MHWPREDICT:
762                 case CF_DEVSEASONAL:
763                 case CF_DEVPREDICT:
764                 case CF_SEASONAL:
765                 case CF_AVERAGE:
766                     newval /= validval;
767                     break;
768                 case CF_MINIMUM:
769                 case CF_FAILURES:
770                 case CF_MAXIMUM:
771                 case CF_LAST:
772                     break;
773                 }
774             }
775             *dstptr++ = newval;
776         }
777         srcptr += (*ds_cnt) * reduce_factor;
778         row_cnt -= reduce_factor;
779     }
780     /* If we had to alter the endtime, we didn't have enough
781      ** source rows to fill the last row. Fill it with NaN.
782      */
783     if (end_offset)
784         for (col = 0; col < (*ds_cnt); col++)
785             *dstptr++ = DNAN;
786 #ifdef DEBUG_REDUCE
787     row_cnt = ((*end) - (*start)) / *step;
788     srcptr = *data;
789     printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
790            row_cnt, *start, *end, *step);
791     for (col = 0; col < row_cnt; col++) {
792         printf("time %10lu: ", *start + (col + 1) * (*step));
793         for (i = 0; i < *ds_cnt; i++)
794             printf(" %8.2e", srcptr[*ds_cnt * col + i]);
795         printf("\n");
796     }
797 #endif
798 }
799
800
801 /* get the data required for the graphs from the
802    relevant rrds ... */
803
804 int data_fetch(
805     image_desc_t *im)
806 {
807     int       i, ii;
808     int       skip;
809
810     /* pull the data from the rrd files ... */
811     for (i = 0; i < (int) im->gdes_c; i++) {
812         /* only GF_DEF elements fetch data */
813         if (im->gdes[i].gf != GF_DEF)
814             continue;
815
816         skip = 0;
817         /* do we have it already ? */
818         for (ii = 0; ii < i; ii++) {
819             if (im->gdes[ii].gf != GF_DEF)
820                 continue;
821             if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
822                 && (im->gdes[i].cf == im->gdes[ii].cf)
823                 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
824                 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
825                 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
826                 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
827                 /* OK, the data is already there.
828                  ** Just copy the header portion
829                  */
830                 im->gdes[i].start = im->gdes[ii].start;
831                 im->gdes[i].end = im->gdes[ii].end;
832                 im->gdes[i].step = im->gdes[ii].step;
833                 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
834                 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
835                 im->gdes[i].data = im->gdes[ii].data;
836                 im->gdes[i].data_first = 0;
837                 skip = 1;
838             }
839             if (skip)
840                 break;
841         }
842         if (!skip) {
843             unsigned long ft_step = im->gdes[i].step;   /* ft_step will record what we got from fetch */
844
845             /* Flush the file if
846              * - a connection to the daemon has been established
847              * - this is the first occurrence of that RRD file
848              */
849             if (rrdc_is_connected(im->daemon_addr))
850             {
851                 int status;
852
853                 status = 0;
854                 for (ii = 0; ii < i; ii++)
855                 {
856                     if (strcmp (im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
857                     {
858                         status = 1;
859                         break;
860                     }
861                 }
862
863                 if (status == 0)
864                 {
865                     status = rrdc_flush (im->gdes[i].rrd);
866                     if (status != 0)
867                     {
868                         rrd_set_error ("rrdc_flush (%s) failed with status %i.",
869                                 im->gdes[i].rrd, status);
870                         return (-1);
871                     }
872                 }
873             } /* if (rrdc_is_connected()) */
874
875             if ((rrd_fetch_fn(im->gdes[i].rrd,
876                               im->gdes[i].cf,
877                               &im->gdes[i].start,
878                               &im->gdes[i].end,
879                               &ft_step,
880                               &im->gdes[i].ds_cnt,
881                               &im->gdes[i].ds_namv,
882                               &im->gdes[i].data)) == -1) {
883                 return -1;
884             }
885             im->gdes[i].data_first = 1;
886
887             if (ft_step < im->gdes[i].step) {
888                 reduce_data(im->gdes[i].cf_reduce,
889                             ft_step,
890                             &im->gdes[i].start,
891                             &im->gdes[i].end,
892                             &im->gdes[i].step,
893                             &im->gdes[i].ds_cnt, &im->gdes[i].data);
894             } else {
895                 im->gdes[i].step = ft_step;
896             }
897         }
898
899         /* lets see if the required data source is really there */
900         for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
901             if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
902                 im->gdes[i].ds = ii;
903             }
904         }
905         if (im->gdes[i].ds == -1) {
906             rrd_set_error("No DS called '%s' in '%s'",
907                           im->gdes[i].ds_nam, im->gdes[i].rrd);
908             return -1;
909         }
910
911     }
912     return 0;
913 }
914
915 /* evaluate the expressions in the CDEF functions */
916
917 /*************************************************************
918  * CDEF stuff
919  *************************************************************/
920
921 long find_var_wrapper(
922     void *arg1,
923     char *key)
924 {
925     return find_var((image_desc_t *) arg1, key);
926 }
927
928 /* find gdes containing var*/
929 long find_var(
930     image_desc_t *im,
931     char *key)
932 {
933     long      ii;
934
935     for (ii = 0; ii < im->gdes_c - 1; ii++) {
936         if ((im->gdes[ii].gf == GF_DEF
937              || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
938             && (strcmp(im->gdes[ii].vname, key) == 0)) {
939             return ii;
940         }
941     }
942     return -1;
943 }
944
945 /* find the greatest common divisor for all the numbers
946    in the 0 terminated num array */
947 long lcd(
948     long *num)
949 {
950     long      rest;
951     int       i;
952
953     for (i = 0; num[i + 1] != 0; i++) {
954         do {
955             rest = num[i] % num[i + 1];
956             num[i] = num[i + 1];
957             num[i + 1] = rest;
958         } while (rest != 0);
959         num[i + 1] = num[i];
960     }
961 /*    return i==0?num[i]:num[i-1]; */
962     return num[i];
963 }
964
965 /* run the rpn calculator on all the VDEF and CDEF arguments */
966 int data_calc(
967     image_desc_t *im)
968 {
969
970     int       gdi;
971     int       dataidx;
972     long     *steparray, rpi;
973     int       stepcnt;
974     time_t    now;
975     rpnstack_t rpnstack;
976
977     rpnstack_init(&rpnstack);
978
979     for (gdi = 0; gdi < im->gdes_c; gdi++) {
980         /* Look for GF_VDEF and GF_CDEF in the same loop,
981          * so CDEFs can use VDEFs and vice versa
982          */
983         switch (im->gdes[gdi].gf) {
984         case GF_XPORT:
985             break;
986         case GF_SHIFT:{
987             graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
988
989             /* remove current shift */
990             vdp->start -= vdp->shift;
991             vdp->end -= vdp->shift;
992
993             /* vdef */
994             if (im->gdes[gdi].shidx >= 0)
995                 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
996             /* constant */
997             else
998                 vdp->shift = im->gdes[gdi].shval;
999
1000             /* normalize shift to multiple of consolidated step */
1001             vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
1002
1003             /* apply shift */
1004             vdp->start += vdp->shift;
1005             vdp->end += vdp->shift;
1006             break;
1007         }
1008         case GF_VDEF:
1009             /* A VDEF has no DS.  This also signals other parts
1010              * of rrdtool that this is a VDEF value, not a CDEF.
1011              */
1012             im->gdes[gdi].ds_cnt = 0;
1013             if (vdef_calc(im, gdi)) {
1014                 rrd_set_error("Error processing VDEF '%s'",
1015                               im->gdes[gdi].vname);
1016                 rpnstack_free(&rpnstack);
1017                 return -1;
1018             }
1019             break;
1020         case GF_CDEF:
1021             im->gdes[gdi].ds_cnt = 1;
1022             im->gdes[gdi].ds = 0;
1023             im->gdes[gdi].data_first = 1;
1024             im->gdes[gdi].start = 0;
1025             im->gdes[gdi].end = 0;
1026             steparray = NULL;
1027             stepcnt = 0;
1028             dataidx = -1;
1029
1030             /* Find the variables in the expression.
1031              * - VDEF variables are substituted by their values
1032              *   and the opcode is changed into OP_NUMBER.
1033              * - CDEF variables are analized for their step size,
1034              *   the lowest common denominator of all the step
1035              *   sizes of the data sources involved is calculated
1036              *   and the resulting number is the step size for the
1037              *   resulting data source.
1038              */
1039             for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1040                 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1041                     im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1042                     long      ptr = im->gdes[gdi].rpnp[rpi].ptr;
1043
1044                     if (im->gdes[ptr].ds_cnt == 0) {    /* this is a VDEF data source */
1045 #if 0
1046                         printf
1047                             ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1048                              im->gdes[gdi].vname, im->gdes[ptr].vname);
1049                         printf("DEBUG: value from vdef is %f\n",
1050                                im->gdes[ptr].vf.val);
1051 #endif
1052                         im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1053                         im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1054                     } else {    /* normal variables and PREF(variables) */
1055
1056                         /* add one entry to the array that keeps track of the step sizes of the
1057                          * data sources going into the CDEF. */
1058                         if ((steparray =
1059                              (long*)rrd_realloc(steparray,
1060                                          (++stepcnt +
1061                                           1) * sizeof(*steparray))) == NULL) {
1062                             rrd_set_error("realloc steparray");
1063                             rpnstack_free(&rpnstack);
1064                             return -1;
1065                         };
1066
1067                         steparray[stepcnt - 1] = im->gdes[ptr].step;
1068
1069                         /* adjust start and end of cdef (gdi) so
1070                          * that it runs from the latest start point
1071                          * to the earliest endpoint of any of the
1072                          * rras involved (ptr)
1073                          */
1074
1075                         if (im->gdes[gdi].start < im->gdes[ptr].start)
1076                             im->gdes[gdi].start = im->gdes[ptr].start;
1077
1078                         if (im->gdes[gdi].end == 0 ||
1079                             im->gdes[gdi].end > im->gdes[ptr].end)
1080                             im->gdes[gdi].end = im->gdes[ptr].end;
1081
1082                         /* store pointer to the first element of
1083                          * the rra providing data for variable,
1084                          * further save step size and data source
1085                          * count of this rra
1086                          */
1087                         im->gdes[gdi].rpnp[rpi].data =
1088                             im->gdes[ptr].data + im->gdes[ptr].ds;
1089                         im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1090                         im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1091
1092                         /* backoff the *.data ptr; this is done so
1093                          * rpncalc() function doesn't have to treat
1094                          * the first case differently
1095                          */
1096                     }   /* if ds_cnt != 0 */
1097                 }       /* if OP_VARIABLE */
1098             }           /* loop through all rpi */
1099
1100             /* move the data pointers to the correct period */
1101             for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1102                 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1103                     im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1104                     long      ptr = im->gdes[gdi].rpnp[rpi].ptr;
1105                     long      diff =
1106                         im->gdes[gdi].start - im->gdes[ptr].start;
1107
1108                     if (diff > 0)
1109                         im->gdes[gdi].rpnp[rpi].data +=
1110                             (diff / im->gdes[ptr].step) *
1111                             im->gdes[ptr].ds_cnt;
1112                 }
1113             }
1114
1115             if (steparray == NULL) {
1116                 rrd_set_error("rpn expressions without DEF"
1117                               " or CDEF variables are not supported");
1118                 rpnstack_free(&rpnstack);
1119                 return -1;
1120             }
1121             steparray[stepcnt] = 0;
1122             /* Now find the resulting step.  All steps in all
1123              * used RRAs have to be visited
1124              */
1125             im->gdes[gdi].step = lcd(steparray);
1126             free(steparray);
1127             if ((im->gdes[gdi].data = (rrd_value_t*)malloc(((im->gdes[gdi].end -
1128                                                im->gdes[gdi].start)
1129                                               / im->gdes[gdi].step)
1130                                              * sizeof(double))) == NULL) {
1131                 rrd_set_error("malloc im->gdes[gdi].data");
1132                 rpnstack_free(&rpnstack);
1133                 return -1;
1134             }
1135
1136             /* Step through the new cdef results array and
1137              * calculate the values
1138              */
1139             for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1140                  now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1141                 rpnp_t   *rpnp = im->gdes[gdi].rpnp;
1142
1143                 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1144                  * in this case we are advancing by timesteps;
1145                  * we use the fact that time_t is a synonym for long
1146                  */
1147                 if (rpn_calc(rpnp, &rpnstack, (long) now,
1148                              im->gdes[gdi].data, ++dataidx) == -1) {
1149                     /* rpn_calc sets the error string */
1150                     rpnstack_free(&rpnstack);
1151                     return -1;
1152                 }
1153             }           /* enumerate over time steps within a CDEF */
1154             break;
1155         default:
1156             continue;
1157         }
1158     }                   /* enumerate over CDEFs */
1159     rpnstack_free(&rpnstack);
1160     return 0;
1161 }
1162
1163 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1164 /* yes we are loosing precision by doing tos with floats instead of doubles
1165    but it seems more stable this way. */
1166
1167 static int AlmostEqual2sComplement(
1168     float A,
1169     float B,
1170     int maxUlps)
1171 {
1172
1173     int       aInt = *(int *) &A;
1174     int       bInt = *(int *) &B;
1175     int       intDiff;
1176
1177     /* Make sure maxUlps is non-negative and small enough that the
1178        default NAN won't compare as equal to anything.  */
1179
1180     /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1181
1182     /* Make aInt lexicographically ordered as a twos-complement int */
1183
1184     if (aInt < 0)
1185         aInt = 0x80000000l - aInt;
1186
1187     /* Make bInt lexicographically ordered as a twos-complement int */
1188
1189     if (bInt < 0)
1190         bInt = 0x80000000l - bInt;
1191
1192     intDiff = abs(aInt - bInt);
1193
1194     if (intDiff <= maxUlps)
1195         return 1;
1196
1197     return 0;
1198 }
1199
1200 /* massage data so, that we get one value for each x coordinate in the graph */
1201 int data_proc(
1202     image_desc_t *im)
1203 {
1204     long      i, ii;
1205     double    pixstep = (double) (im->end - im->start)
1206         / (double) im->xsize;   /* how much time
1207                                    passes in one pixel */
1208     double    paintval;
1209     double    minval = DNAN, maxval = DNAN;
1210
1211     unsigned long gr_time;
1212
1213     /* memory for the processed data */
1214     for (i = 0; i < im->gdes_c; i++) {
1215         if ((im->gdes[i].gf == GF_LINE) ||
1216             (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1217             if ((im->gdes[i].p_data = (rrd_value_t*)malloc((im->xsize + 1)
1218                                              * sizeof(rrd_value_t))) == NULL) {
1219                 rrd_set_error("malloc data_proc");
1220                 return -1;
1221             }
1222         }
1223     }
1224
1225     for (i = 0; i < im->xsize; i++) {   /* for each pixel */
1226         long      vidx;
1227
1228         gr_time = im->start + pixstep * i;  /* time of the current step */
1229         paintval = 0.0;
1230
1231         for (ii = 0; ii < im->gdes_c; ii++) {
1232             double    value;
1233
1234             switch (im->gdes[ii].gf) {
1235             case GF_LINE:
1236             case GF_AREA:
1237             case GF_TICK:
1238                 if (!im->gdes[ii].stack)
1239                     paintval = 0.0;
1240                 value = im->gdes[ii].yrule;
1241                 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1242                     /* The time of the data doesn't necessarily match
1243                      ** the time of the graph. Beware.
1244                      */
1245                     vidx = im->gdes[ii].vidx;
1246                     if (im->gdes[vidx].gf == GF_VDEF) {
1247                         value = im->gdes[vidx].vf.val;
1248                     } else
1249                         if (((long int) gr_time >=
1250                              (long int) im->gdes[vidx].start)
1251                             && ((long int) gr_time <
1252                                 (long int) im->gdes[vidx].end)) {
1253                         value = im->gdes[vidx].data[(unsigned long)
1254                                                     floor((double)
1255                                                           (gr_time -
1256                                                            im->gdes[vidx].
1257                                                            start)
1258                                                           /
1259                                                           im->gdes[vidx].step)
1260                                                     * im->gdes[vidx].ds_cnt +
1261                                                     im->gdes[vidx].ds];
1262                     } else {
1263                         value = DNAN;
1264                     }
1265                 };
1266
1267                 if (!isnan(value)) {
1268                     paintval += value;
1269                     im->gdes[ii].p_data[i] = paintval;
1270                     /* GF_TICK: the data values are not
1271                      ** relevant for min and max
1272                      */
1273                     if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1274                         if ((isnan(minval) || paintval < minval) &&
1275                             !(im->logarithmic && paintval <= 0.0))
1276                             minval = paintval;
1277                         if (isnan(maxval) || paintval > maxval)
1278                             maxval = paintval;
1279                     }
1280                 } else {
1281                     im->gdes[ii].p_data[i] = DNAN;
1282                 }
1283                 break;
1284             case GF_STACK:
1285                 rrd_set_error
1286                     ("STACK should already be turned into LINE or AREA here");
1287                 return -1;
1288                 break;
1289             default:
1290                 break;
1291             }
1292         }
1293     }
1294
1295     /* if min or max have not been asigned a value this is because
1296        there was no data in the graph ... this is not good ...
1297        lets set these to dummy values then ... */
1298
1299     if (im->logarithmic) {
1300         if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1301             minval = 0.0;   /* catching this right away below */
1302             maxval = 5.1;
1303         }
1304         /* in logarithm mode, where minval is smaller or equal
1305            to 0 make the beast just way smaller than maxval */
1306         if (minval <= 0) {
1307             minval = maxval / 10e8;
1308         }
1309     } else {
1310         if (isnan(minval) || isnan(maxval)) {
1311             minval = 0.0;
1312             maxval = 1.0;
1313         }
1314     }
1315
1316     /* adjust min and max values given by the user */
1317     /* for logscale we add something on top */
1318     if (isnan(im->minval)
1319         || ((!im->rigid) && im->minval > minval)
1320         ) {
1321         if (im->logarithmic)
1322             im->minval = minval / 2.0;
1323         else
1324             im->minval = minval;
1325     }
1326     if (isnan(im->maxval)
1327         || (!im->rigid && im->maxval < maxval)
1328         ) {
1329         if (im->logarithmic)
1330             im->maxval = maxval * 2.0;
1331         else
1332             im->maxval = maxval;
1333     }
1334
1335     /* make sure min is smaller than max */
1336     if (im->minval > im->maxval) {
1337         if (im->minval > 0)
1338             im->minval = 0.99 * im->maxval;
1339         else
1340             im->minval = 1.01 * im->maxval;
1341     }
1342
1343     /* make sure min and max are not equal */
1344     if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1345         if (im->maxval > 0)
1346             im->maxval *= 1.01;
1347         else
1348             im->maxval *= 0.99;
1349
1350         /* make sure min and max are not both zero */
1351         if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1352             im->maxval = 1.0;
1353         }
1354     }
1355     return 0;
1356 }
1357
1358
1359
1360 /* identify the point where the first gridline, label ... gets placed */
1361
1362 time_t find_first_time(
1363     time_t start,       /* what is the initial time */
1364     enum tmt_en baseint,    /* what is the basic interval */
1365     long basestep       /* how many if these do we jump a time */
1366     )
1367 {
1368     struct tm tm;
1369
1370     localtime_r(&start, &tm);
1371
1372     switch (baseint) {
1373     case TMT_SECOND:
1374         tm.       tm_sec -= tm.tm_sec % basestep;
1375
1376         break;
1377     case TMT_MINUTE:
1378         tm.       tm_sec = 0;
1379         tm.       tm_min -= tm.tm_min % basestep;
1380
1381         break;
1382     case TMT_HOUR:
1383         tm.       tm_sec = 0;
1384         tm.       tm_min = 0;
1385         tm.       tm_hour -= tm.tm_hour % basestep;
1386
1387         break;
1388     case TMT_DAY:
1389         /* we do NOT look at the basestep for this ... */
1390         tm.       tm_sec = 0;
1391         tm.       tm_min = 0;
1392         tm.       tm_hour = 0;
1393
1394         break;
1395     case TMT_WEEK:
1396         /* we do NOT look at the basestep for this ... */
1397         tm.       tm_sec = 0;
1398         tm.       tm_min = 0;
1399         tm.       tm_hour = 0;
1400         tm.       tm_mday -= tm.tm_wday - 1;    /* -1 because we want the monday */
1401
1402         if (tm.tm_wday == 0)
1403             tm.       tm_mday -= 7; /* we want the *previous* monday */
1404
1405         break;
1406     case TMT_MONTH:
1407         tm.       tm_sec = 0;
1408         tm.       tm_min = 0;
1409         tm.       tm_hour = 0;
1410         tm.       tm_mday = 1;
1411         tm.       tm_mon -= tm.tm_mon % basestep;
1412
1413         break;
1414
1415     case TMT_YEAR:
1416         tm.       tm_sec = 0;
1417         tm.       tm_min = 0;
1418         tm.       tm_hour = 0;
1419         tm.       tm_mday = 1;
1420         tm.       tm_mon = 0;
1421         tm.       tm_year -= (
1422     tm.tm_year + 1900) %basestep;
1423
1424     }
1425     return mktime(&tm);
1426 }
1427
1428 /* identify the point where the next gridline, label ... gets placed */
1429 time_t find_next_time(
1430     time_t current,     /* what is the initial time */
1431     enum tmt_en baseint,    /* what is the basic interval */
1432     long basestep       /* how many if these do we jump a time */
1433     )
1434 {
1435     struct tm tm;
1436     time_t    madetime;
1437
1438     localtime_r(&current, &tm);
1439
1440     do {
1441         switch (baseint) {
1442         case TMT_SECOND:
1443             tm.       tm_sec += basestep;
1444
1445             break;
1446         case TMT_MINUTE:
1447             tm.       tm_min += basestep;
1448
1449             break;
1450         case TMT_HOUR:
1451             tm.       tm_hour += basestep;
1452
1453             break;
1454         case TMT_DAY:
1455             tm.       tm_mday += basestep;
1456
1457             break;
1458         case TMT_WEEK:
1459             tm.       tm_mday += 7 * basestep;
1460
1461             break;
1462         case TMT_MONTH:
1463             tm.       tm_mon += basestep;
1464
1465             break;
1466         case TMT_YEAR:
1467             tm.       tm_year += basestep;
1468         }
1469         madetime = mktime(&tm);
1470     } while (madetime == -1);   /* this is necessary to skip impssible times
1471                                    like the daylight saving time skips */
1472     return madetime;
1473
1474 }
1475
1476
1477 /* calculate values required for PRINT and GPRINT functions */
1478
1479 int print_calc(
1480     image_desc_t *im)
1481 {
1482     long      i, ii, validsteps;
1483     double    printval;
1484     struct tm tmvdef;
1485     int       graphelement = 0;
1486     long      vidx;
1487     int       max_ii;
1488     double    magfact = -1;
1489     char     *si_symb = "";
1490     char     *percent_s;
1491     int       prline_cnt = 0;
1492
1493     /* wow initializing tmvdef is quite a task :-) */
1494     time_t    now = time(NULL);
1495
1496     localtime_r(&now, &tmvdef);
1497     for (i = 0; i < im->gdes_c; i++) {
1498         vidx = im->gdes[i].vidx;
1499         switch (im->gdes[i].gf) {
1500         case GF_PRINT:
1501         case GF_GPRINT:
1502             /* PRINT and GPRINT can now print VDEF generated values.
1503              * There's no need to do any calculations on them as these
1504              * calculations were already made.
1505              */
1506             if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1507                 printval = im->gdes[vidx].vf.val;
1508                 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1509             } else {    /* need to calculate max,min,avg etcetera */
1510                 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1511                           / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1512                 printval = DNAN;
1513                 validsteps = 0;
1514                 for (ii = im->gdes[vidx].ds;
1515                      ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1516                     if (!finite(im->gdes[vidx].data[ii]))
1517                         continue;
1518                     if (isnan(printval)) {
1519                         printval = im->gdes[vidx].data[ii];
1520                         validsteps++;
1521                         continue;
1522                     }
1523
1524                     switch (im->gdes[i].cf) {
1525                     case CF_HWPREDICT:
1526                     case CF_MHWPREDICT:
1527                     case CF_DEVPREDICT:
1528                     case CF_DEVSEASONAL:
1529                     case CF_SEASONAL:
1530                     case CF_AVERAGE:
1531                         validsteps++;
1532                         printval += im->gdes[vidx].data[ii];
1533                         break;
1534                     case CF_MINIMUM:
1535                         printval = min(printval, im->gdes[vidx].data[ii]);
1536                         break;
1537                     case CF_FAILURES:
1538                     case CF_MAXIMUM:
1539                         printval = max(printval, im->gdes[vidx].data[ii]);
1540                         break;
1541                     case CF_LAST:
1542                         printval = im->gdes[vidx].data[ii];
1543                     }
1544                 }
1545                 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1546                     if (validsteps > 1) {
1547                         printval = (printval / validsteps);
1548                     }
1549                 }
1550             }           /* prepare printval */
1551
1552             if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1553                 /* Magfact is set to -1 upon entry to print_calc.  If it
1554                  * is still less than 0, then we need to run auto_scale.
1555                  * Otherwise, put the value into the correct units.  If
1556                  * the value is 0, then do not set the symbol or magnification
1557                  * so next the calculation will be performed again. */
1558                 if (magfact < 0.0) {
1559                     auto_scale(im, &printval, &si_symb, &magfact);
1560                     if (printval == 0.0)
1561                         magfact = -1.0;
1562                 } else {
1563                     printval /= magfact;
1564                 }
1565                 *(++percent_s) = 's';
1566             } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1567                 auto_scale(im, &printval, &si_symb, &magfact);
1568             }
1569
1570             if (im->gdes[i].gf == GF_PRINT) {
1571                 rrd_infoval_t prline;
1572
1573                 if (im->gdes[i].strftm) {
1574                     prline.u_str = (char*)malloc((FMT_LEG_LEN + 2) * sizeof(char));
1575                     strftime(prline.u_str,
1576                              FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1577                 } else if (bad_format(im->gdes[i].format)) {
1578                     rrd_set_error
1579                         ("bad format for PRINT in '%s'", im->gdes[i].format);
1580                     return -1;
1581                 } else {
1582                     prline.u_str =
1583                         sprintf_alloc(im->gdes[i].format, printval, si_symb);
1584                 }
1585                 grinfo_push(im,
1586                             sprintf_alloc
1587                             ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1588                 free(prline.u_str);
1589             } else {
1590                 /* GF_GPRINT */
1591
1592                 if (im->gdes[i].strftm) {
1593                     strftime(im->gdes[i].legend,
1594                              FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1595                 } else {
1596                     if (bad_format(im->gdes[i].format)) {
1597                         rrd_set_error
1598                             ("bad format for GPRINT in '%s'",
1599                              im->gdes[i].format);
1600                         return -1;
1601                     }
1602 #ifdef HAVE_SNPRINTF
1603                     snprintf(im->gdes[i].legend,
1604                              FMT_LEG_LEN - 2,
1605                              im->gdes[i].format, printval, si_symb);
1606 #else
1607                     sprintf(im->gdes[i].legend,
1608                             im->gdes[i].format, printval, si_symb);
1609 #endif
1610                 }
1611                 graphelement = 1;
1612             }
1613             break;
1614         case GF_LINE:
1615         case GF_AREA:
1616         case GF_TICK:
1617             graphelement = 1;
1618             break;
1619         case GF_HRULE:
1620             if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1621                 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1622             };
1623             graphelement = 1;
1624             break;
1625         case GF_VRULE:
1626             if (im->gdes[i].xrule == 0) {   /* again ... the legend printer needs it */
1627                 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1628             };
1629             graphelement = 1;
1630             break;
1631         case GF_COMMENT:
1632         case GF_TEXTALIGN:
1633         case GF_DEF:
1634         case GF_CDEF:
1635         case GF_VDEF:
1636 #ifdef WITH_PIECHART
1637         case GF_PART:
1638 #endif
1639         case GF_SHIFT:
1640         case GF_XPORT:
1641             break;
1642         case GF_STACK:
1643             rrd_set_error
1644                 ("STACK should already be turned into LINE or AREA here");
1645             return -1;
1646             break;
1647         }
1648     }
1649     return graphelement;
1650 }
1651
1652
1653
1654 /* place legends with color spots */
1655 int leg_place(
1656     image_desc_t *im,
1657     int calc_width)
1658 {
1659     /* graph labels */
1660     int       interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1661     int       border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1662     int       fill = 0, fill_last;
1663     double    legendwidth; // = im->ximg - 2 * border;
1664     int       leg_c = 0;
1665     double    leg_x = border;
1666     int       leg_y = 0; //im->yimg;
1667     int       leg_y_prev = 0; // im->yimg;
1668     int       leg_cc;
1669     double    glue = 0;
1670     int       i, ii, mark = 0;
1671     char      default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1672     int      *legspace;
1673     char     *tab;
1674     char      saved_legend[FMT_LEG_LEN + 5];
1675
1676     if(calc_width){
1677         legendwidth = 0;
1678     }
1679     else{
1680         legendwidth = im->legendwidth - 2 * border;
1681     }
1682
1683
1684     if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
1685         if ((legspace = (int*)malloc(im->gdes_c * sizeof(int))) == NULL) {
1686             rrd_set_error("malloc for legspace");
1687             return -1;
1688         }
1689
1690         for (i = 0; i < im->gdes_c; i++) {
1691             char      prt_fctn; /*special printfunctions */
1692             if(calc_width){
1693                 strcpy(saved_legend, im->gdes[i].legend);
1694             }
1695
1696             fill_last = fill;
1697             /* hide legends for rules which are not displayed */
1698             if (im->gdes[i].gf == GF_TEXTALIGN) {
1699                 default_txtalign = im->gdes[i].txtalign;
1700             }
1701
1702             if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1703                 if (im->gdes[i].gf == GF_HRULE
1704                     && (im->gdes[i].yrule <
1705                         im->minval || im->gdes[i].yrule > im->maxval))
1706                     im->gdes[i].legend[0] = '\0';
1707                 if (im->gdes[i].gf == GF_VRULE
1708                     && (im->gdes[i].xrule <
1709                         im->start || im->gdes[i].xrule > im->end))
1710                     im->gdes[i].legend[0] = '\0';
1711             }
1712
1713             /* turn \\t into tab */
1714             while ((tab = strstr(im->gdes[i].legend, "\\t"))) {
1715                 memmove(tab, tab + 1, strlen(tab));
1716                 tab[0] = (char) 9;
1717             }
1718
1719             leg_cc = strlen(im->gdes[i].legend);
1720             /* is there a controle code at the end of the legend string ? */
1721             if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\') {
1722                 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1723                 leg_cc -= 2;
1724                 im->gdes[i].legend[leg_cc] = '\0';
1725             } else {
1726                 prt_fctn = '\0';
1727             }
1728             /* only valid control codes */
1729             if (prt_fctn != 'l' && prt_fctn != 'n' &&   /* a synonym for l */
1730                 prt_fctn != 'r' &&
1731                 prt_fctn != 'j' &&
1732                 prt_fctn != 'c' &&
1733                 prt_fctn != 'u' &&
1734                 prt_fctn != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1735                 free(legspace);
1736                 rrd_set_error
1737                     ("Unknown control code at the end of '%s\\%c'",
1738                      im->gdes[i].legend, prt_fctn);
1739                 return -1;
1740             }
1741             /* \n -> \l */
1742             if (prt_fctn == 'n') {
1743                 prt_fctn = 'l';
1744             }
1745
1746             /* remove exess space from the end of the legend for \g */
1747             while (prt_fctn == 'g' &&
1748                    leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1749                 leg_cc--;
1750                 im->gdes[i].legend[leg_cc] = '\0';
1751             }
1752
1753             if (leg_cc != 0) {
1754
1755                 /* no interleg space if string ends in \g */
1756                 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1757                 if (fill > 0) {
1758                     fill += legspace[i];
1759                 }
1760                 fill +=
1761                     gfx_get_text_width(im,
1762                                        fill + border,
1763                                        im->
1764                                        text_prop
1765                                        [TEXT_PROP_LEGEND].
1766                                        font_desc,
1767                                        im->tabwidth, im->gdes[i].legend);
1768                 leg_c++;
1769             } else {
1770                 legspace[i] = 0;
1771             }
1772             /* who said there was a special tag ... ? */
1773             if (prt_fctn == 'g') {
1774                 prt_fctn = '\0';
1775             }
1776
1777             if (prt_fctn == '\0') {
1778                 if(calc_width && (fill > legendwidth)){
1779                     legendwidth = fill;
1780                 }
1781                 if (i == im->gdes_c - 1 || fill > legendwidth) {
1782                     /* just one legend item is left right or center */
1783                     switch (default_txtalign) {
1784                     case TXA_RIGHT:
1785                         prt_fctn = 'r';
1786                         break;
1787                     case TXA_CENTER:
1788                         prt_fctn = 'c';
1789                         break;
1790                     case TXA_JUSTIFIED:
1791                         prt_fctn = 'j';
1792                         break;
1793                     default:
1794                         prt_fctn = 'l';
1795                         break;
1796                     }
1797                 }
1798                 /* is it time to place the legends ? */
1799                 if (fill > legendwidth) {
1800                     if (leg_c > 1) {
1801                         /* go back one */
1802                         i--;
1803                         fill = fill_last;
1804                         leg_c--;
1805                     }
1806                 }
1807                 if (leg_c == 1 && prt_fctn == 'j') {
1808                     prt_fctn = 'l';
1809                 }
1810             }
1811
1812             if (prt_fctn != '\0') {
1813                 leg_x = border;
1814                 if (leg_c >= 2 && prt_fctn == 'j') {
1815                     glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1816                 } else {
1817                     glue = 0;
1818                 }
1819                 if (prt_fctn == 'c')
1820                     leg_x = (double)(legendwidth - fill) / 2.0;
1821                 if (prt_fctn == 'r')
1822                     leg_x = legendwidth - fill + border;
1823                 for (ii = mark; ii <= i; ii++) {
1824                     if (im->gdes[ii].legend[0] == '\0')
1825                         continue;   /* skip empty legends */
1826                     im->gdes[ii].leg_x = leg_x;
1827                     im->gdes[ii].leg_y = leg_y + border;
1828                     leg_x +=
1829                         (double)gfx_get_text_width(im, leg_x,
1830                                            im->
1831                                            text_prop
1832                                            [TEXT_PROP_LEGEND].
1833                                            font_desc,
1834                                            im->tabwidth, im->gdes[ii].legend)
1835                         +(double)legspace[ii]
1836                         + glue;
1837                 }
1838                 leg_y_prev = leg_y;
1839                 if (leg_x > border || prt_fctn == 's')
1840                     leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1841                 if (prt_fctn == 's')
1842                     leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1843                 if (prt_fctn == 'u')
1844                     leg_y -= im->text_prop[TEXT_PROP_LEGEND].size *1.8;
1845
1846                 if(calc_width && (fill > legendwidth)){
1847                     legendwidth = fill;
1848                 }
1849                 fill = 0;
1850                 leg_c = 0;
1851                 mark = ii;
1852             }
1853
1854             if(calc_width){
1855                 strcpy(im->gdes[i].legend, saved_legend);
1856             }
1857         }
1858
1859         if(calc_width){
1860             im->legendwidth = legendwidth + 2 * border;
1861         }
1862         else{
1863             im->legendheight = leg_y + border * 0.6;
1864         }
1865         free(legspace);
1866     }
1867     return 0;
1868 }
1869
1870 /* create a grid on the graph. it determines what to do
1871    from the values of xsize, start and end */
1872
1873 /* the xaxis labels are determined from the number of seconds per pixel
1874    in the requested graph */
1875
1876 int calc_horizontal_grid(
1877     image_desc_t
1878     *im)
1879 {
1880     double    range;
1881     double    scaledrange;
1882     int       pixel, i;
1883     int       gridind = 0;
1884     int       decimals, fractionals;
1885
1886     im->ygrid_scale.labfact = 2;
1887     range = im->maxval - im->minval;
1888     scaledrange = range / im->magfact;
1889     /* does the scale of this graph make it impossible to put lines
1890        on it? If so, give up. */
1891     if (isnan(scaledrange)) {
1892         return 0;
1893     }
1894
1895     /* find grid spaceing */
1896     pixel = 1;
1897     if (isnan(im->ygridstep)) {
1898         if (im->extra_flags & ALTYGRID) {
1899             /* find the value with max number of digits. Get number of digits */
1900             decimals =
1901                 ceil(log10
1902                      (max(fabs(im->maxval), fabs(im->minval)) *
1903                       im->viewfactor / im->magfact));
1904             if (decimals <= 0)  /* everything is small. make place for zero */
1905                 decimals = 1;
1906             im->ygrid_scale.gridstep =
1907                 pow((double) 10,
1908                     floor(log10(range * im->viewfactor / im->magfact))) /
1909                 im->viewfactor * im->magfact;
1910             if (im->ygrid_scale.gridstep == 0)  /* range is one -> 0.1 is reasonable scale */
1911                 im->ygrid_scale.gridstep = 0.1;
1912             /* should have at least 5 lines but no more then 15 */
1913             if (range / im->ygrid_scale.gridstep < 5
1914                 && im->ygrid_scale.gridstep >= 30)
1915                 im->ygrid_scale.gridstep /= 10;
1916             if (range / im->ygrid_scale.gridstep > 15)
1917                 im->ygrid_scale.gridstep *= 10;
1918             if (range / im->ygrid_scale.gridstep > 5) {
1919                 im->ygrid_scale.labfact = 1;
1920                 if (range / im->ygrid_scale.gridstep > 8
1921                     || im->ygrid_scale.gridstep <
1922                     1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1923                     im->ygrid_scale.labfact = 2;
1924             } else {
1925                 im->ygrid_scale.gridstep /= 5;
1926                 im->ygrid_scale.labfact = 5;
1927             }
1928             fractionals =
1929                 floor(log10
1930                       (im->ygrid_scale.gridstep *
1931                        (double) im->ygrid_scale.labfact * im->viewfactor /
1932                        im->magfact));
1933             if (fractionals < 0) {  /* small amplitude. */
1934                 int       len = decimals - fractionals + 1;
1935
1936                 if (im->unitslength < len + 2)
1937                     im->unitslength = len + 2;
1938                 sprintf(im->ygrid_scale.labfmt,
1939                         "%%%d.%df%s", len,
1940                         -fractionals, (im->symbol != ' ' ? " %c" : ""));
1941             } else {
1942                 int       len = decimals + 1;
1943
1944                 if (im->unitslength < len + 2)
1945                     im->unitslength = len + 2;
1946                 sprintf(im->ygrid_scale.labfmt,
1947                         "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1948             }
1949         } else {        /* classic rrd grid */
1950             for (i = 0; ylab[i].grid > 0; i++) {
1951                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1952                 gridind = i;
1953                 if (pixel >= 5)
1954                     break;
1955             }
1956
1957             for (i = 0; i < 4; i++) {
1958                 if (pixel * ylab[gridind].lfac[i] >=
1959                     1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1960                     im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1961                     break;
1962                 }
1963             }
1964
1965             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1966         }
1967     } else {
1968         im->ygrid_scale.gridstep = im->ygridstep;
1969         im->ygrid_scale.labfact = im->ylabfact;
1970     }
1971     return 1;
1972 }
1973
1974 int draw_horizontal_grid(
1975     image_desc_t
1976     *im)
1977 {
1978     int       i;
1979     double    scaledstep;
1980     char      graph_label[100];
1981     int       nlabels = 0;
1982     double    X0 = im->xorigin;
1983     double    X1 = im->xorigin + im->xsize;
1984     int       sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1985     int       egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1986     double    MaxY;
1987     double second_axis_magfact = 0;
1988     char *second_axis_symb = "";
1989
1990     scaledstep =
1991         im->ygrid_scale.gridstep /
1992         (double) im->magfact * (double) im->viewfactor;
1993     MaxY = scaledstep * (double) egrid;
1994     for (i = sgrid; i <= egrid; i++) {
1995         double    Y0 = ytr(im,
1996                            im->ygrid_scale.gridstep * i);
1997         double    YN = ytr(im,
1998                            im->ygrid_scale.gridstep * (i + 1));
1999
2000         if (floor(Y0 + 0.5) >=
2001             im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
2002             /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2003                with the chosen settings. Add a label if required by settings, or if
2004                there is only one label so far and the next grid line is out of bounds. */
2005             if (i % im->ygrid_scale.labfact == 0
2006                 || (nlabels == 1
2007                     && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2008                 if (im->symbol == ' ') {
2009                     if (im->extra_flags & ALTYGRID) {
2010                         sprintf(graph_label,
2011                                 im->ygrid_scale.labfmt,
2012                                 scaledstep * (double) i);
2013                     } else {
2014                         if (MaxY < 10) {
2015                             sprintf(graph_label, "%4.1f",
2016                                     scaledstep * (double) i);
2017                         } else {
2018                             sprintf(graph_label, "%4.0f",
2019                                     scaledstep * (double) i);
2020                         }
2021                     }
2022                 } else {
2023                     char      sisym = (i == 0 ? ' ' : im->symbol);
2024
2025                     if (im->extra_flags & ALTYGRID) {
2026                         sprintf(graph_label,
2027                                 im->ygrid_scale.labfmt,
2028                                 scaledstep * (double) i, sisym);
2029                     } else {
2030                         if (MaxY < 10) {
2031                             sprintf(graph_label, "%4.1f %c",
2032                                     scaledstep * (double) i, sisym);
2033                         } else {
2034                             sprintf(graph_label, "%4.0f %c",
2035                                     scaledstep * (double) i, sisym);
2036                         }
2037                     }
2038                 }
2039                 nlabels++;
2040                 if (im->second_axis_scale != 0){
2041                         char graph_label_right[100];
2042                         double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2043                         if (im->second_axis_format[0] == '\0'){
2044                             if (!second_axis_magfact){
2045                                 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2046                                 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2047                             }
2048                             sval /= second_axis_magfact;
2049
2050                             if(MaxY < 10) {
2051                                 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2052                             } else {
2053                                 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2054                             }
2055                         }
2056                         else {
2057                            sprintf(graph_label_right,im->second_axis_format,sval);
2058                         }
2059                         gfx_text ( im,
2060                                X1+7, Y0,
2061                                im->graph_col[GRC_FONT],
2062                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2063                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2064                                graph_label_right );
2065                 }
2066
2067                 gfx_text(im,
2068                          X0 -
2069                          im->
2070                          text_prop[TEXT_PROP_AXIS].
2071                          size, Y0,
2072                          im->graph_col[GRC_FONT],
2073                          im->
2074                          text_prop[TEXT_PROP_AXIS].
2075                          font_desc,
2076                          im->tabwidth, 0.0,
2077                          GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2078                 gfx_line(im, X0 - 2, Y0, X0, Y0,
2079                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2080                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2081                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2082                 gfx_dashed_line(im, X0 - 2, Y0,
2083                                 X1 + 2, Y0,
2084                                 MGRIDWIDTH,
2085                                 im->
2086                                 graph_col
2087                                 [GRC_MGRID],
2088                                 im->grid_dash_on, im->grid_dash_off);
2089             } else if (!(im->extra_flags & NOMINOR)) {
2090                 gfx_line(im,
2091                          X0 - 2, Y0,
2092                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2093                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2094                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2095                 gfx_dashed_line(im, X0 - 1, Y0,
2096                                 X1 + 1, Y0,
2097                                 GRIDWIDTH,
2098                                 im->
2099                                 graph_col[GRC_GRID],
2100                                 im->grid_dash_on, im->grid_dash_off);
2101             }
2102         }
2103     }
2104     return 1;
2105 }
2106
2107 /* this is frexp for base 10 */
2108 double    frexp10(
2109     double,
2110     double *);
2111 double frexp10(
2112     double x,
2113     double *e)
2114 {
2115     double    mnt;
2116     int       iexp;
2117
2118     iexp = floor(log((double)fabs(x)) / log((double)10));
2119     mnt = x / pow(10.0, iexp);
2120     if (mnt >= 10.0) {
2121         iexp++;
2122         mnt = x / pow(10.0, iexp);
2123     }
2124     *e = iexp;
2125     return mnt;
2126 }
2127
2128
2129 /* logaritmic horizontal grid */
2130 int horizontal_log_grid(
2131     image_desc_t
2132     *im)
2133 {
2134     double    yloglab[][10] = {
2135         {
2136          1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2137          0.0, 0.0, 0.0}, {
2138                           1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2139                           0.0, 0.0, 0.0}, {
2140                                            1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2141                                            0.0, 0.0, 0.0}, {
2142                                                             1.0, 2.0, 4.0,
2143                                                             6.0, 8.0, 10.,
2144                                                             0.0,
2145                                                             0.0, 0.0, 0.0}, {
2146                                                                              1.0,
2147                                                                              2.0,
2148                                                                              3.0,
2149                                                                              4.0,
2150                                                                              5.0,
2151                                                                              6.0,
2152                                                                              7.0,
2153                                                                              8.0,
2154                                                                              9.0,
2155                                                                              10.},
2156         {
2157          0, 0, 0, 0, 0, 0, 0, 0, 0, 0}  /* last line */
2158     };
2159     int       i, j, val_exp, min_exp;
2160     double    nex;      /* number of decades in data */
2161     double    logscale; /* scale in logarithmic space */
2162     int       exfrac = 1;   /* decade spacing */
2163     int       mid = -1; /* row in yloglab for major grid */
2164     double    mspac;    /* smallest major grid spacing (pixels) */
2165     int       flab;     /* first value in yloglab to use */
2166     double    value, tmp, pre_value;
2167     double    X0, X1, Y0;
2168     char      graph_label[100];
2169
2170     nex = log10(im->maxval / im->minval);
2171     logscale = im->ysize / nex;
2172     /* major spacing for data with high dynamic range */
2173     while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2174         if (exfrac == 1)
2175             exfrac = 3;
2176         else
2177             exfrac += 3;
2178     }
2179
2180     /* major spacing for less dynamic data */
2181     do {
2182         /* search best row in yloglab */
2183         mid++;
2184         for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2185         mspac = logscale * log10(10.0 / yloglab[mid][i]);
2186     }
2187     while (mspac >
2188            2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2189     if (mid)
2190         mid--;
2191     /* find first value in yloglab */
2192     for (flab = 0;
2193          yloglab[mid][flab] < 10
2194          && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2195     if (yloglab[mid][flab] == 10.0) {
2196         tmp += 1.0;
2197         flab = 0;
2198     }
2199     val_exp = tmp;
2200     if (val_exp % exfrac)
2201         val_exp += abs(-val_exp % exfrac);
2202     X0 = im->xorigin;
2203     X1 = im->xorigin + im->xsize;
2204     /* draw grid */
2205     pre_value = DNAN;
2206     while (1) {
2207
2208         value = yloglab[mid][flab] * pow(10.0, val_exp);
2209         if (AlmostEqual2sComplement(value, pre_value, 4))
2210             break;      /* it seems we are not converging */
2211         pre_value = value;
2212         Y0 = ytr(im, value);
2213         if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2214             break;
2215         /* major grid line */
2216         gfx_line(im,
2217                  X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2218         gfx_line(im, X1, Y0, X1 + 2, Y0,
2219                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2220         gfx_dashed_line(im, X0 - 2, Y0,
2221                         X1 + 2, Y0,
2222                         MGRIDWIDTH,
2223                         im->
2224                         graph_col
2225                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2226         /* label */
2227         if (im->extra_flags & FORCE_UNITS_SI) {
2228             int       scale;
2229             double    pvalue;
2230             char      symbol;
2231
2232             scale = floor(val_exp / 3.0);
2233             if (value >= 1.0)
2234                 pvalue = pow(10.0, val_exp % 3);
2235             else
2236                 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2237             pvalue *= yloglab[mid][flab];
2238             if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2239                 && ((scale + si_symbcenter) >= 0))
2240                 symbol = si_symbol[scale + si_symbcenter];
2241             else
2242                 symbol = '?';
2243             sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2244         } else {
2245             sprintf(graph_label, "%3.0e", value);
2246         }
2247         if (im->second_axis_scale != 0){
2248                 char graph_label_right[100];
2249                 double sval = value*im->second_axis_scale+im->second_axis_shift;
2250                 if (im->second_axis_format[0] == '\0'){
2251                         if (im->extra_flags & FORCE_UNITS_SI) {
2252                                 double mfac = 1;
2253                                 char   *symb = "";
2254                                 auto_scale(im,&sval,&symb,&mfac);
2255                                 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2256                         }
2257                         else {
2258                                 sprintf(graph_label_right,"%3.0e", sval);
2259                         }
2260                 }
2261                 else {
2262                       sprintf(graph_label_right,im->second_axis_format,sval);
2263                 }
2264
2265                 gfx_text ( im,
2266                                X1+7, Y0,
2267                                im->graph_col[GRC_FONT],
2268                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2269                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2270                                graph_label_right );
2271         }
2272
2273         gfx_text(im,
2274                  X0 -
2275                  im->
2276                  text_prop[TEXT_PROP_AXIS].
2277                  size, Y0,
2278                  im->graph_col[GRC_FONT],
2279                  im->
2280                  text_prop[TEXT_PROP_AXIS].
2281                  font_desc,
2282                  im->tabwidth, 0.0,
2283                  GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2284         /* minor grid */
2285         if (mid < 4 && exfrac == 1) {
2286             /* find first and last minor line behind current major line
2287              * i is the first line and j tha last */
2288             if (flab == 0) {
2289                 min_exp = val_exp - 1;
2290                 for (i = 1; yloglab[mid][i] < 10.0; i++);
2291                 i = yloglab[mid][i - 1] + 1;
2292                 j = 10;
2293             } else {
2294                 min_exp = val_exp;
2295                 i = yloglab[mid][flab - 1] + 1;
2296                 j = yloglab[mid][flab];
2297             }
2298
2299             /* draw minor lines below current major line */
2300             for (; i < j; i++) {
2301
2302                 value = i * pow(10.0, min_exp);
2303                 if (value < im->minval)
2304                     continue;
2305                 Y0 = ytr(im, value);
2306                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2307                     break;
2308                 /* draw lines */
2309                 gfx_line(im,
2310                          X0 - 2, Y0,
2311                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2312                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2313                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2314                 gfx_dashed_line(im, X0 - 1, Y0,
2315                                 X1 + 1, Y0,
2316                                 GRIDWIDTH,
2317                                 im->
2318                                 graph_col[GRC_GRID],
2319                                 im->grid_dash_on, im->grid_dash_off);
2320             }
2321         } else if (exfrac > 1) {
2322             for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2323                 value = pow(10.0, i);
2324                 if (value < im->minval)
2325                     continue;
2326                 Y0 = ytr(im, value);
2327                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2328                     break;
2329                 /* draw lines */
2330                 gfx_line(im,
2331                          X0 - 2, Y0,
2332                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2333                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2334                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2335                 gfx_dashed_line(im, X0 - 1, Y0,
2336                                 X1 + 1, Y0,
2337                                 GRIDWIDTH,
2338                                 im->
2339                                 graph_col[GRC_GRID],
2340                                 im->grid_dash_on, im->grid_dash_off);
2341             }
2342         }
2343
2344         /* next decade */
2345         if (yloglab[mid][++flab] == 10.0) {
2346             flab = 0;
2347             val_exp += exfrac;
2348         }
2349     }
2350
2351     /* draw minor lines after highest major line */
2352     if (mid < 4 && exfrac == 1) {
2353         /* find first and last minor line below current major line
2354          * i is the first line and j tha last */
2355         if (flab == 0) {
2356             min_exp = val_exp - 1;
2357             for (i = 1; yloglab[mid][i] < 10.0; i++);
2358             i = yloglab[mid][i - 1] + 1;
2359             j = 10;
2360         } else {
2361             min_exp = val_exp;
2362             i = yloglab[mid][flab - 1] + 1;
2363             j = yloglab[mid][flab];
2364         }
2365
2366         /* draw minor lines below current major line */
2367         for (; i < j; i++) {
2368
2369             value = i * pow(10.0, min_exp);
2370             if (value < im->minval)
2371                 continue;
2372             Y0 = ytr(im, value);
2373             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2374                 break;
2375             /* draw lines */
2376             gfx_line(im,
2377                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2378             gfx_line(im, X1, Y0, X1 + 2, Y0,
2379                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2380             gfx_dashed_line(im, X0 - 1, Y0,
2381                             X1 + 1, Y0,
2382                             GRIDWIDTH,
2383                             im->
2384                             graph_col[GRC_GRID],
2385                             im->grid_dash_on, im->grid_dash_off);
2386         }
2387     }
2388     /* fancy minor gridlines */
2389     else if (exfrac > 1) {
2390         for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2391             value = pow(10.0, i);
2392             if (value < im->minval)
2393                 continue;
2394             Y0 = ytr(im, value);
2395             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2396                 break;
2397             /* draw lines */
2398             gfx_line(im,
2399                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2400             gfx_line(im, X1, Y0, X1 + 2, Y0,
2401                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2402             gfx_dashed_line(im, X0 - 1, Y0,
2403                             X1 + 1, Y0,
2404                             GRIDWIDTH,
2405                             im->
2406                             graph_col[GRC_GRID],
2407                             im->grid_dash_on, im->grid_dash_off);
2408         }
2409     }
2410
2411     return 1;
2412 }
2413
2414
2415 void vertical_grid(
2416     image_desc_t *im)
2417 {
2418     int       xlab_sel; /* which sort of label and grid ? */
2419     time_t    ti, tilab, timajor;
2420     long      factor;
2421     char      graph_label[100];
2422     double    X0, Y0, Y1;   /* points for filled graph and more */
2423     struct tm tm;
2424
2425     /* the type of time grid is determined by finding
2426        the number of seconds per pixel in the graph */
2427     if (im->xlab_user.minsec == -1) {
2428         factor = (im->end - im->start) / im->xsize;
2429         xlab_sel = 0;
2430         while (xlab[xlab_sel + 1].minsec !=
2431                -1 && xlab[xlab_sel + 1].minsec <= factor) {
2432             xlab_sel++;
2433         }               /* pick the last one */
2434         while (xlab[xlab_sel - 1].minsec ==
2435                xlab[xlab_sel].minsec
2436                && xlab[xlab_sel].length > (im->end - im->start)) {
2437             xlab_sel--;
2438         }               /* go back to the smallest size */
2439         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2440         im->xlab_user.gridst = xlab[xlab_sel].gridst;
2441         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2442         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2443         im->xlab_user.labtm = xlab[xlab_sel].labtm;
2444         im->xlab_user.labst = xlab[xlab_sel].labst;
2445         im->xlab_user.precis = xlab[xlab_sel].precis;
2446         im->xlab_user.stst = xlab[xlab_sel].stst;
2447     }
2448
2449     /* y coords are the same for every line ... */
2450     Y0 = im->yorigin;
2451     Y1 = im->yorigin - im->ysize;
2452     /* paint the minor grid */
2453     if (!(im->extra_flags & NOMINOR)) {
2454         for (ti = find_first_time(im->start,
2455                                   im->
2456                                   xlab_user.
2457                                   gridtm,
2458                                   im->
2459                                   xlab_user.
2460                                   gridst),
2461              timajor =
2462              find_first_time(im->start,
2463                              im->xlab_user.
2464                              mgridtm,
2465                              im->xlab_user.
2466                              mgridst);
2467              ti < im->end;
2468              ti =
2469              find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2470             ) {
2471             /* are we inside the graph ? */
2472             if (ti < im->start || ti > im->end)
2473                 continue;
2474             while (timajor < ti) {
2475                 timajor = find_next_time(timajor,
2476                                          im->
2477                                          xlab_user.
2478                                          mgridtm, im->xlab_user.mgridst);
2479             }
2480             if (ti == timajor)
2481                 continue;   /* skip as falls on major grid line */
2482             X0 = xtr(im, ti);
2483             gfx_line(im, X0, Y1 - 2, X0, Y1,
2484                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2485             gfx_line(im, X0, Y0, X0, Y0 + 2,
2486                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2487             gfx_dashed_line(im, X0, Y0 + 1, X0,
2488                             Y1 - 1, GRIDWIDTH,
2489                             im->
2490                             graph_col[GRC_GRID],
2491                             im->grid_dash_on, im->grid_dash_off);
2492         }
2493     }
2494
2495     /* paint the major grid */
2496     for (ti = find_first_time(im->start,
2497                               im->
2498                               xlab_user.
2499                               mgridtm,
2500                               im->
2501                               xlab_user.
2502                               mgridst);
2503          ti < im->end;
2504          ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2505         ) {
2506         /* are we inside the graph ? */
2507         if (ti < im->start || ti > im->end)
2508             continue;
2509         X0 = xtr(im, ti);
2510         gfx_line(im, X0, Y1 - 2, X0, Y1,
2511                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2512         gfx_line(im, X0, Y0, X0, Y0 + 3,
2513                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2514         gfx_dashed_line(im, X0, Y0 + 3, X0,
2515                         Y1 - 2, MGRIDWIDTH,
2516                         im->
2517                         graph_col
2518                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2519     }
2520     /* paint the labels below the graph */
2521     for (ti =
2522          find_first_time(im->start -
2523                          im->xlab_user.
2524                          precis / 2,
2525                          im->xlab_user.
2526                          labtm,
2527                          im->xlab_user.
2528                          labst);
2529          ti <=
2530          im->end -
2531          im->xlab_user.precis / 2;
2532          ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2533         ) {
2534         tilab = ti + im->xlab_user.precis / 2;  /* correct time for the label */
2535         /* are we inside the graph ? */
2536         if (tilab < im->start || tilab > im->end)
2537             continue;
2538 #if HAVE_STRFTIME
2539         localtime_r(&tilab, &tm);
2540         strftime(graph_label, 99, im->xlab_user.stst, &tm);
2541 #else
2542 # error "your libc has no strftime I guess we'll abort the exercise here."
2543 #endif
2544         gfx_text(im,
2545                  xtr(im, tilab),
2546                  Y0 + 3,
2547                  im->graph_col[GRC_FONT],
2548                  im->
2549                  text_prop[TEXT_PROP_AXIS].
2550                  font_desc,
2551                  im->tabwidth, 0.0,
2552                  GFX_H_CENTER, GFX_V_TOP, graph_label);
2553     }
2554
2555 }
2556
2557
2558 void axis_paint(
2559     image_desc_t *im)
2560 {
2561     /* draw x and y axis */
2562     /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2563        im->xorigin+im->xsize,im->yorigin-im->ysize,
2564        GRIDWIDTH, im->graph_col[GRC_AXIS]);
2565
2566        gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2567        im->xorigin+im->xsize,im->yorigin-im->ysize,
2568        GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2569
2570     gfx_line(im, im->xorigin - 4,
2571              im->yorigin,
2572              im->xorigin + im->xsize +
2573              4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2574     gfx_line(im, im->xorigin,
2575              im->yorigin + 4,
2576              im->xorigin,
2577              im->yorigin - im->ysize -
2578              4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2579     /* arrow for X and Y axis direction */
2580     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 */
2581                  im->graph_col[GRC_ARROW]);
2582     gfx_close_path(im);
2583     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 */
2584                  im->graph_col[GRC_ARROW]);
2585     gfx_close_path(im);
2586     if (im->second_axis_scale != 0){
2587        gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2588                          im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2589                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2590        gfx_new_area ( im,
2591                    im->xorigin+im->xsize-2,  im->yorigin-im->ysize-2,
2592                    im->xorigin+im->xsize+3,  im->yorigin-im->ysize-2,
2593                    im->xorigin+im->xsize,    im->yorigin-im->ysize-7, /* LINEOFFSET */
2594                    im->graph_col[GRC_ARROW]);
2595        gfx_close_path(im);
2596     }
2597
2598 }
2599
2600 void grid_paint(
2601     image_desc_t *im)
2602 {
2603     long      i;
2604     int       res = 0;
2605     double    X0, Y0;   /* points for filled graph and more */
2606     struct gfx_color_t water_color;
2607
2608     if (im->draw_3d_border > 0) {
2609             /* draw 3d border */
2610             i = im->draw_3d_border;
2611             gfx_new_area(im, 0, im->yimg,
2612                          i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2613             gfx_add_point(im, im->ximg - i, i);
2614             gfx_add_point(im, im->ximg, 0);
2615             gfx_add_point(im, 0, 0);
2616             gfx_close_path(im);
2617             gfx_new_area(im, i, im->yimg - i,
2618                          im->ximg - i,
2619                          im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2620             gfx_add_point(im, im->ximg, 0);
2621             gfx_add_point(im, im->ximg, im->yimg);
2622             gfx_add_point(im, 0, im->yimg);
2623             gfx_close_path(im);
2624     }
2625     if (im->draw_x_grid == 1)
2626         vertical_grid(im);
2627     if (im->draw_y_grid == 1) {
2628         if (im->logarithmic) {
2629             res = horizontal_log_grid(im);
2630         } else {
2631             res = draw_horizontal_grid(im);
2632         }
2633
2634         /* dont draw horizontal grid if there is no min and max val */
2635         if (!res) {
2636             char     *nodata = "No Data found";
2637
2638             gfx_text(im, im->ximg / 2,
2639                      (2 * im->yorigin -
2640                       im->ysize) / 2,
2641                      im->graph_col[GRC_FONT],
2642                      im->
2643                      text_prop[TEXT_PROP_AXIS].
2644                      font_desc,
2645                      im->tabwidth, 0.0,
2646                      GFX_H_CENTER, GFX_V_CENTER, nodata);
2647         }
2648     }
2649
2650     /* yaxis unit description */
2651     if (im->ylegend[0] != '\0'){
2652         gfx_text(im,
2653                  im->xOriginLegendY+10,
2654                  im->yOriginLegendY,
2655                  im->graph_col[GRC_FONT],
2656                  im->
2657                  text_prop[TEXT_PROP_UNIT].
2658                  font_desc,
2659                  im->tabwidth,
2660                  RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2661
2662     }
2663     if (im->second_axis_legend[0] != '\0'){
2664             gfx_text( im,
2665                   im->xOriginLegendY2+10,
2666                   im->yOriginLegendY2,
2667                   im->graph_col[GRC_FONT],
2668                   im->text_prop[TEXT_PROP_UNIT].font_desc,
2669                   im->tabwidth,
2670                   RRDGRAPH_YLEGEND_ANGLE,
2671                   GFX_H_CENTER, GFX_V_CENTER,
2672                   im->second_axis_legend);
2673     }
2674
2675     /* graph title */
2676     gfx_text(im,
2677              im->xOriginTitle, im->yOriginTitle+6,
2678              im->graph_col[GRC_FONT],
2679              im->
2680              text_prop[TEXT_PROP_TITLE].
2681              font_desc,
2682              im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2683     /* rrdtool 'logo' */
2684     if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2685         water_color = im->graph_col[GRC_FONT];
2686         water_color.alpha = 0.3;
2687         double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2688         gfx_text(im, xpos, 5,
2689                  water_color,
2690                  im->
2691                  text_prop[TEXT_PROP_WATERMARK].
2692                  font_desc, im->tabwidth,
2693                  -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2694     }
2695     /* graph watermark */
2696     if (im->watermark[0] != '\0') {
2697         water_color = im->graph_col[GRC_FONT];
2698         water_color.alpha = 0.3;
2699         gfx_text(im,
2700                  im->ximg / 2, im->yimg - 6,
2701                  water_color,
2702                  im->
2703                  text_prop[TEXT_PROP_WATERMARK].
2704                  font_desc, im->tabwidth, 0,
2705                  GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2706     }
2707
2708     /* graph labels */
2709     if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2710         for (i = 0; i < im->gdes_c; i++) {
2711             if (im->gdes[i].legend[0] == '\0')
2712                 continue;
2713             /* im->gdes[i].leg_y is the bottom of the legend */
2714             X0 = im->xOriginLegend + im->gdes[i].leg_x;
2715             Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2716             gfx_text(im, X0, Y0,
2717                      im->graph_col[GRC_FONT],
2718                      im->
2719                      text_prop
2720                      [TEXT_PROP_LEGEND].font_desc,
2721                      im->tabwidth, 0.0,
2722                      GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2723             /* The legend for GRAPH items starts with "M " to have
2724                enough space for the box */
2725             if (im->gdes[i].gf != GF_PRINT &&
2726                 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2727                 double    boxH, boxV;
2728                 double    X1, Y1;
2729
2730                 boxH = gfx_get_text_width(im, 0,
2731                                           im->
2732                                           text_prop
2733                                           [TEXT_PROP_LEGEND].
2734                                           font_desc,
2735                                           im->tabwidth, "o") * 1.2;
2736                 boxV = boxH;
2737                 /* shift the box up a bit */
2738                 Y0 -= boxV * 0.4;
2739
2740         if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */ 
2741                         cairo_save(im->cr);
2742                         cairo_new_path(im->cr);
2743                         cairo_set_line_width(im->cr, 1.0);
2744                         gfx_line(im,
2745                                 X0, Y0 - boxV / 2,
2746                                 X0 + boxH, Y0 - boxV / 2,
2747                                 1.0, im->gdes[i].col);
2748                         gfx_close_path(im);
2749                 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2750                         cairo_save(im->cr);
2751                         cairo_new_path(im->cr);
2752                         cairo_set_line_width(im->cr, 1.0);
2753                         gfx_line(im,
2754                                 X0 + boxH / 2, Y0,
2755                                 X0 + boxH / 2, Y0 - boxV,
2756                                 1.0, im->gdes[i].col);
2757                         gfx_close_path(im);
2758                 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2759                         cairo_save(im->cr);
2760                         cairo_new_path(im->cr);
2761                         cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2762                         gfx_line(im,
2763                                 X0, Y0,
2764                                 X0 + boxH, Y0 - boxV,
2765                                 im->gdes[i].linewidth, im->gdes[i].col);
2766                         gfx_close_path(im);
2767                 } else {
2768                 /* make sure transparent colors show up the same way as in the graph */
2769                         gfx_new_area(im,
2770                                      X0, Y0 - boxV,
2771                                      X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2772                         gfx_add_point(im, X0 + boxH, Y0 - boxV);
2773                         gfx_close_path(im);
2774                         gfx_new_area(im, X0, Y0 - boxV, X0,
2775                                      Y0, X0 + boxH, Y0, im->gdes[i].col);
2776                         gfx_add_point(im, X0 + boxH, Y0 - boxV);
2777                         gfx_close_path(im);
2778                         cairo_save(im->cr);
2779                         cairo_new_path(im->cr);
2780                         cairo_set_line_width(im->cr, 1.0);
2781                         X1 = X0 + boxH;
2782                         Y1 = Y0 - boxV;
2783                         gfx_line_fit(im, &X0, &Y0);
2784                         gfx_line_fit(im, &X1, &Y1);
2785                         cairo_move_to(im->cr, X0, Y0);
2786                         cairo_line_to(im->cr, X1, Y0);
2787                         cairo_line_to(im->cr, X1, Y1);
2788                         cairo_line_to(im->cr, X0, Y1);
2789                         cairo_close_path(im->cr);
2790                         cairo_set_source_rgba(im->cr,
2791                                               im->graph_col[GRC_FRAME].red,
2792                                               im->graph_col[GRC_FRAME].green,
2793                                               im->graph_col[GRC_FRAME].blue,
2794                                               im->graph_col[GRC_FRAME].alpha);
2795                 }
2796                 if (im->gdes[i].dash) {
2797                     /* make box borders in legend dashed if the graph is dashed */
2798                     double    dashes[] = {
2799                         3.0
2800                     };
2801                     cairo_set_dash(im->cr, dashes, 1, 0.0);
2802                 }
2803                 cairo_stroke(im->cr);
2804                 cairo_restore(im->cr);
2805             }
2806         }
2807     }
2808 }
2809
2810
2811 /*****************************************************
2812  * lazy check make sure we rely need to create this graph
2813  *****************************************************/
2814
2815 int lazy_check(
2816     image_desc_t *im)
2817 {
2818     FILE     *fd = NULL;
2819     int       size = 1;
2820     struct stat imgstat;
2821
2822     if (im->lazy == 0)
2823         return 0;       /* no lazy option */
2824     if (strlen(im->graphfile) == 0)
2825         return 0;       /* inmemory option */
2826     if (stat(im->graphfile, &imgstat) != 0)
2827         return 0;       /* can't stat */
2828     /* one pixel in the existing graph is more then what we would
2829        change here ... */
2830     if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2831         return 0;
2832     if ((fd = fopen(im->graphfile, "rb")) == NULL)
2833         return 0;       /* the file does not exist */
2834     switch (im->imgformat) {
2835     case IF_PNG:
2836         size = PngSize(fd, &(im->ximg), &(im->yimg));
2837         break;
2838     default:
2839         size = 1;
2840     }
2841     fclose(fd);
2842     return size;
2843 }
2844
2845
2846 int graph_size_location(
2847     image_desc_t
2848     *im,
2849     int elements)
2850 {
2851     /* The actual size of the image to draw is determined from
2852      ** several sources.  The size given on the command line is
2853      ** the graph area but we need more as we have to draw labels
2854      ** and other things outside the graph area. If the option
2855      ** --full-size-mode is selected the size defines the total
2856      ** image size and the size available for the graph is
2857      ** calculated.
2858      */
2859
2860     /** +---+-----------------------------------+
2861      ** | y |...............graph title.........|
2862      ** |   +---+-------------------------------+
2863      ** | a | y |                               |
2864      ** | x |   |                               |
2865      ** | i | a |                               |
2866      ** | s | x |       main graph area         |
2867      ** |   | i |                               |
2868      ** | t | s |                               |
2869      ** | i |   |                               |
2870      ** | t | l |                               |
2871      ** | l | b +-------------------------------+
2872      ** | e | l |       x axis labels           |
2873      ** +---+---+-------------------------------+
2874      ** |....................legends............|
2875      ** +---------------------------------------+
2876      ** |                   watermark           |
2877      ** +---------------------------------------+
2878      */
2879
2880     int       Xvertical = 0, Xvertical2 = 0, Ytitle =
2881         0, Xylabel = 0, Xmain = 0, Ymain =
2882         0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2883
2884     // no legends and no the shall be plotted it's easy
2885     if (im->extra_flags & ONLY_GRAPH) {
2886         im->xorigin = 0;
2887         im->ximg = im->xsize;
2888         im->yimg = im->ysize;
2889         im->yorigin = im->ysize;
2890         ytr(im, DNAN);
2891         return 0;
2892     }
2893
2894     if(im->watermark[0] != '\0') {
2895         Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2896     }
2897
2898     // calculate the width of the left vertical legend
2899     if (im->ylegend[0] != '\0') {
2900         Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2901     }
2902
2903     // calculate the width of the right vertical legend
2904     if (im->second_axis_legend[0] != '\0') {
2905         Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2906     }
2907     else{
2908         Xvertical2 = Xspacing;
2909     }
2910
2911     if (im->title[0] != '\0') {
2912         /* The title is placed "inbetween" two text lines so it
2913          ** automatically has some vertical spacing.  The horizontal
2914          ** spacing is added here, on each side.
2915          */
2916         /* if necessary, reduce the font size of the title until it fits the image width */
2917         Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2918     }
2919     else{
2920         // we have no title; get a little clearing from the top
2921         Ytitle = 1.5 * Yspacing;
2922     }
2923
2924     if (elements) {
2925         if (im->draw_x_grid) {
2926             // calculate the height of the horizontal labelling
2927             Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2928         }
2929         if (im->draw_y_grid || im->forceleftspace) {
2930             // calculate the width of the vertical labelling
2931             Xylabel =
2932                 gfx_get_text_width(im, 0,
2933                                    im->text_prop[TEXT_PROP_AXIS].font_desc,
2934                                    im->tabwidth, "0") * im->unitslength;
2935         }
2936     }
2937
2938     // add some space to the labelling
2939     Xylabel += Xspacing;
2940
2941     /* If the legend is printed besides the graph the width has to be
2942      ** calculated first. Placing the legend north or south of the
2943      ** graph requires the width calculation first, so the legend is
2944      ** skipped for the moment.
2945      */
2946     im->legendheight = 0;
2947     im->legendwidth = 0;
2948     if (!(im->extra_flags & NOLEGEND)) {
2949         if(im->legendposition == WEST || im->legendposition == EAST){
2950             if (leg_place(im, 1) == -1){
2951                 return -1;
2952             }
2953         }
2954     }
2955
2956     if (im->extra_flags & FULL_SIZE_MODE) {
2957
2958         /* The actual size of the image to draw has been determined by the user.
2959          ** The graph area is the space remaining after accounting for the legend,
2960          ** the watermark, the axis labels, and the title.
2961          */
2962         im->ximg = im->xsize;
2963         im->yimg = im->ysize;
2964         Xmain = im->ximg;
2965         Ymain = im->yimg;
2966
2967         /* Now calculate the total size.  Insert some spacing where
2968            desired.  im->xorigin and im->yorigin need to correspond
2969            with the lower left corner of the main graph area or, if
2970            this one is not set, the imaginary box surrounding the
2971            pie chart area. */
2972         /* Initial size calculation for the main graph area */
2973
2974         Xmain -= Xylabel;// + Xspacing;
2975         if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
2976             Xmain -= im->legendwidth;// + Xspacing;
2977         }
2978         if (im->second_axis_scale != 0){
2979             Xmain -= Xylabel;
2980         }
2981         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2982             Xmain -= Xspacing;
2983         }
2984
2985         Xmain -= Xvertical + Xvertical2;
2986
2987         /* limit the remaining space to 0 */
2988         if(Xmain < 1){
2989             Xmain = 1;
2990         }
2991         im->xsize = Xmain;
2992
2993         /* Putting the legend north or south, the height can now be calculated */
2994         if (!(im->extra_flags & NOLEGEND)) {
2995             if(im->legendposition == NORTH || im->legendposition == SOUTH){
2996                 im->legendwidth = im->ximg;
2997                 if (leg_place(im, 0) == -1){
2998                     return -1;
2999                 }
3000             }
3001         }
3002
3003         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3004             Ymain -=  Yxlabel + im->legendheight;
3005         }
3006         else{
3007             Ymain -= Yxlabel;
3008         }
3009
3010         /* reserve space for the title *or* some padding above the graph */
3011         Ymain -= Ytitle;
3012
3013             /* reserve space for padding below the graph */
3014         if (im->extra_flags & NOLEGEND) {
3015             Ymain -= Yspacing;
3016         }
3017
3018         if (im->watermark[0] != '\0') {
3019             Ymain -= Ywatermark;
3020         }
3021         /* limit the remaining height to 0 */
3022         if(Ymain < 1){
3023             Ymain = 1;
3024         }
3025         im->ysize = Ymain;
3026     } else {            /* dimension options -width and -height refer to the dimensions of the main graph area */
3027
3028         /* The actual size of the image to draw is determined from
3029          ** several sources.  The size given on the command line is
3030          ** the graph area but we need more as we have to draw labels
3031          ** and other things outside the graph area.
3032          */
3033
3034         if (elements) {
3035             Xmain = im->xsize; // + Xspacing;
3036             Ymain = im->ysize;
3037         }
3038
3039         im->ximg = Xmain + Xylabel;
3040         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3041             im->ximg += Xspacing;
3042         }
3043
3044         if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3045             im->ximg += im->legendwidth;// + Xspacing;
3046         }
3047         if (im->second_axis_scale != 0){
3048             im->ximg += Xylabel;
3049         }
3050
3051         im->ximg += Xvertical + Xvertical2;
3052
3053         if (!(im->extra_flags & NOLEGEND)) {
3054             if(im->legendposition == NORTH || im->legendposition == SOUTH){
3055                 im->legendwidth = im->ximg;
3056                 if (leg_place(im, 0) == -1){
3057                     return -1;
3058                 }
3059             }
3060         }
3061
3062         im->yimg = Ymain + Yxlabel;
3063         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3064              im->yimg += im->legendheight;
3065         }
3066
3067         /* reserve space for the title *or* some padding above the graph */
3068         if (Ytitle) {
3069             im->yimg += Ytitle;
3070         } else {
3071             im->yimg += 1.5 * Yspacing;
3072         }
3073         /* reserve space for padding below the graph */
3074         if (im->extra_flags & NOLEGEND) {
3075             im->yimg += Yspacing;
3076         }
3077
3078         if (im->watermark[0] != '\0') {
3079             im->yimg += Ywatermark;
3080         }
3081     }
3082
3083
3084     /* In case of putting the legend in west or east position the first
3085      ** legend calculation might lead to wrong positions if some items
3086      ** are not aligned on the left hand side (e.g. centered) as the
3087      ** legendwidth wight have been increased after the item was placed.
3088      ** In this case the positions have to be recalculated.
3089      */
3090     if (!(im->extra_flags & NOLEGEND)) {
3091         if(im->legendposition == WEST || im->legendposition == EAST){
3092             if (leg_place(im, 0) == -1){
3093                 return -1;
3094             }
3095         }
3096     }
3097
3098     /* After calculating all dimensions
3099      ** it is now possible to calculate
3100      ** all offsets.
3101      */
3102     switch(im->legendposition){
3103         case NORTH:
3104             im->xOriginTitle   = Xvertical + Xylabel + (im->xsize / 2);
3105             im->yOriginTitle   = 0;
3106
3107             im->xOriginLegend  = 0;
3108             im->yOriginLegend  = Ytitle;
3109
3110             im->xOriginLegendY = 0;
3111             im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3112
3113             im->xorigin        = Xvertical + Xylabel;
3114             im->yorigin        = Ytitle + im->legendheight + Ymain;
3115
3116             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3117             if (im->second_axis_scale != 0){
3118                 im->xOriginLegendY2 += Xylabel;
3119             }
3120             im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3121
3122             break;
3123
3124         case WEST:
3125             im->xOriginTitle   = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3126             im->yOriginTitle   = 0;
3127
3128             im->xOriginLegend  = 0;
3129             im->yOriginLegend  = Ytitle;
3130
3131             im->xOriginLegendY = im->legendwidth;
3132             im->yOriginLegendY = Ytitle + (Ymain / 2);
3133
3134             im->xorigin        = im->legendwidth + Xvertical + Xylabel;
3135             im->yorigin        = Ytitle + Ymain;
3136
3137             im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3138             if (im->second_axis_scale != 0){
3139                 im->xOriginLegendY2 += Xylabel;
3140             }
3141             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3142
3143             break;
3144
3145         case SOUTH:
3146             im->xOriginTitle   = Xvertical + Xylabel + im->xsize / 2;
3147             im->yOriginTitle   = 0;
3148
3149             im->xOriginLegend  = 0;
3150             im->yOriginLegend  = Ytitle + Ymain + Yxlabel;
3151
3152             im->xOriginLegendY = 0;
3153             im->yOriginLegendY = Ytitle + (Ymain / 2);
3154
3155             im->xorigin        = Xvertical + Xylabel;
3156             im->yorigin        = Ytitle + Ymain;
3157
3158             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3159             if (im->second_axis_scale != 0){
3160                 im->xOriginLegendY2 += Xylabel;
3161             }
3162             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3163
3164             break;
3165
3166         case EAST:
3167             im->xOriginTitle   = Xvertical + Xylabel + im->xsize / 2;
3168             im->yOriginTitle   = 0;
3169
3170             im->xOriginLegend  = Xvertical + Xylabel + Xmain + Xvertical2;
3171             if (im->second_axis_scale != 0){
3172                 im->xOriginLegend += Xylabel;
3173             }
3174             im->yOriginLegend  = Ytitle;
3175
3176             im->xOriginLegendY = 0;
3177             im->yOriginLegendY = Ytitle + (Ymain / 2);
3178
3179             im->xorigin        = Xvertical + Xylabel;
3180             im->yorigin        = Ytitle + Ymain;
3181
3182             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3183             if (im->second_axis_scale != 0){
3184                 im->xOriginLegendY2 += Xylabel;
3185             }
3186             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3187
3188             if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3189                 im->xOriginTitle    += Xspacing;
3190                 im->xOriginLegend   += Xspacing;
3191                 im->xOriginLegendY  += Xspacing;
3192                 im->xorigin         += Xspacing;
3193                 im->xOriginLegendY2 += Xspacing;
3194             }
3195             break;
3196     }
3197
3198     xtr(im, 0);
3199     ytr(im, DNAN);
3200     return 0;
3201 }
3202
3203 static cairo_status_t cairo_output(
3204     void *closure,
3205     const unsigned char
3206     *data,
3207     unsigned int length)
3208 {
3209     image_desc_t *im = (image_desc_t*)closure;
3210
3211     im->rendered_image =
3212         (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3213     if (im->rendered_image == NULL)
3214         return CAIRO_STATUS_WRITE_ERROR;
3215     memcpy(im->rendered_image + im->rendered_image_size, data, length);
3216     im->rendered_image_size += length;
3217     return CAIRO_STATUS_SUCCESS;
3218 }
3219
3220 /* draw that picture thing ... */
3221 int graph_paint(
3222     image_desc_t *im)
3223 {
3224     int       i, ii;
3225     int       lazy = lazy_check(im);
3226     double    areazero = 0.0;
3227     graph_desc_t *lastgdes = NULL;
3228     rrd_infoval_t info;
3229
3230 //    PangoFontMap *font_map = pango_cairo_font_map_get_default();
3231
3232     /* pull the data from the rrd files ... */
3233     if (data_fetch(im) == -1)
3234         return -1;
3235     /* evaluate VDEF and CDEF operations ... */
3236     if (data_calc(im) == -1)
3237         return -1;
3238     /* calculate and PRINT and GPRINT definitions. We have to do it at
3239      * this point because it will affect the length of the legends
3240      * if there are no graph elements (i==0) we stop here ...
3241      * if we are lazy, try to quit ...
3242      */
3243     i = print_calc(im);
3244     if (i < 0)
3245         return -1;
3246
3247     /* if we want and can be lazy ... quit now */
3248     if (i == 0)
3249         return 0;
3250
3251 /**************************************************************
3252  *** Calculating sizes and locations became a bit confusing ***
3253  *** so I moved this into a separate function.              ***
3254  **************************************************************/
3255     if (graph_size_location(im, i) == -1)
3256         return -1;
3257
3258     info.u_cnt = im->xorigin;
3259     grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3260     info.u_cnt = im->yorigin - im->ysize;
3261     grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3262     info.u_cnt = im->xsize;
3263     grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3264     info.u_cnt = im->ysize;
3265     grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3266     info.u_cnt = im->ximg;
3267     grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3268     info.u_cnt = im->yimg;
3269     grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3270     info.u_cnt = im->start;
3271     grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3272     info.u_cnt = im->end;
3273     grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3274
3275     /* if we want and can be lazy ... quit now */
3276     if (lazy)
3277         return 0;
3278
3279     /* get actual drawing data and find min and max values */
3280     if (data_proc(im) == -1)
3281         return -1;
3282     if (!im->logarithmic) {
3283         si_unit(im);
3284     }
3285
3286     /* identify si magnitude Kilo, Mega Giga ? */
3287     if (!im->rigid && !im->logarithmic)
3288         expand_range(im);   /* make sure the upper and lower limit are
3289                                sensible values */
3290
3291     info.u_val = im->minval;
3292     grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3293     info.u_val = im->maxval;
3294     grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3295
3296
3297     if (!calc_horizontal_grid(im))
3298         return -1;
3299     /* reset precalc */
3300     ytr(im, DNAN);
3301 /*   if (im->gridfit)
3302      apply_gridfit(im); */
3303     /* the actual graph is created by going through the individual
3304        graph elements and then drawing them */
3305     cairo_surface_destroy(im->surface);
3306     switch (im->imgformat) {
3307     case IF_PNG:
3308         im->surface =
3309             cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3310                                        im->ximg * im->zoom,
3311                                        im->yimg * im->zoom);
3312         break;
3313     case IF_PDF:
3314         im->gridfit = 0;
3315         im->surface = strlen(im->graphfile)
3316             ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3317                                        im->yimg * im->zoom)
3318             : cairo_pdf_surface_create_for_stream
3319             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3320         break;
3321     case IF_EPS:
3322         im->gridfit = 0;
3323         im->surface = strlen(im->graphfile)
3324             ?
3325             cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3326                                     im->yimg * im->zoom)
3327             : cairo_ps_surface_create_for_stream
3328             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3329         break;
3330     case IF_SVG:
3331         im->gridfit = 0;
3332         im->surface = strlen(im->graphfile)
3333             ?
3334             cairo_svg_surface_create(im->
3335                                      graphfile,
3336                                      im->ximg * im->zoom, im->yimg * im->zoom)
3337             : cairo_svg_surface_create_for_stream
3338             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3339         cairo_svg_surface_restrict_to_version
3340             (im->surface, CAIRO_SVG_VERSION_1_1);
3341         break;
3342     };
3343     cairo_destroy(im->cr);
3344     im->cr = cairo_create(im->surface);
3345     cairo_set_antialias(im->cr, im->graph_antialias);
3346     cairo_scale(im->cr, im->zoom, im->zoom);
3347 //    pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3348     gfx_new_area(im, 0, 0, 0, im->yimg,
3349                  im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3350     gfx_add_point(im, im->ximg, 0);
3351     gfx_close_path(im);
3352     gfx_new_area(im, im->xorigin,
3353                  im->yorigin,
3354                  im->xorigin +
3355                  im->xsize, im->yorigin,
3356                  im->xorigin +
3357                  im->xsize,
3358                  im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3359     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3360     gfx_close_path(im);
3361     cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3362                     im->xsize, im->ysize + 2.0);
3363     cairo_clip(im->cr);
3364     if (im->minval > 0.0)
3365         areazero = im->minval;
3366     if (im->maxval < 0.0)
3367         areazero = im->maxval;
3368     for (i = 0; i < im->gdes_c; i++) {
3369         switch (im->gdes[i].gf) {
3370         case GF_CDEF:
3371         case GF_VDEF:
3372         case GF_DEF:
3373         case GF_PRINT:
3374         case GF_GPRINT:
3375         case GF_COMMENT:
3376         case GF_TEXTALIGN:
3377         case GF_HRULE:
3378         case GF_VRULE:
3379         case GF_XPORT:
3380         case GF_SHIFT:
3381             break;
3382         case GF_TICK:
3383             for (ii = 0; ii < im->xsize; ii++) {
3384                 if (!isnan(im->gdes[i].p_data[ii])
3385                     && im->gdes[i].p_data[ii] != 0.0) {
3386                     if (im->gdes[i].yrule > 0) {
3387                         gfx_line(im,
3388                                  im->xorigin + ii,
3389                                  im->yorigin + 1.0,
3390                                  im->xorigin + ii,
3391                                  im->yorigin -
3392                                  im->gdes[i].yrule *
3393                                  im->ysize, 1.0, im->gdes[i].col);
3394                     } else if (im->gdes[i].yrule < 0) {
3395                         gfx_line(im,
3396                                  im->xorigin + ii,
3397                                  im->yorigin - im->ysize - 1.0,
3398                                  im->xorigin + ii,
3399                                  im->yorigin - im->ysize -
3400                                                 im->gdes[i].
3401                                                 yrule *
3402                                  im->ysize, 1.0, im->gdes[i].col);
3403                     }
3404                 }
3405             }
3406             break;
3407         case GF_LINE:
3408         case GF_AREA:
3409             /* fix data points at oo and -oo */
3410             for (ii = 0; ii < im->xsize; ii++) {
3411                 if (isinf(im->gdes[i].p_data[ii])) {
3412                     if (im->gdes[i].p_data[ii] > 0) {
3413                         im->gdes[i].p_data[ii] = im->maxval;
3414                     } else {
3415                         im->gdes[i].p_data[ii] = im->minval;
3416                     }
3417
3418                 }
3419             }           /* for */
3420
3421             /* *******************************************************
3422                a           ___. (a,t)
3423                |   |    ___
3424                ____|   |   |   |
3425                |       |___|
3426                -------|--t-1--t--------------------------------
3427
3428                if we know the value at time t was a then
3429                we draw a square from t-1 to t with the value a.
3430
3431                ********************************************************* */
3432             if (im->gdes[i].col.alpha != 0.0) {
3433                 /* GF_LINE and friend */
3434                 if (im->gdes[i].gf == GF_LINE) {
3435                     double    last_y = 0.0;
3436                     int       draw_on = 0;
3437
3438                     cairo_save(im->cr);
3439                     cairo_new_path(im->cr);
3440                     cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3441                     if (im->gdes[i].dash) {
3442                         cairo_set_dash(im->cr,
3443                                        im->gdes[i].p_dashes,
3444                                        im->gdes[i].ndash, im->gdes[i].offset);
3445                     }
3446
3447                     for (ii = 1; ii < im->xsize; ii++) {
3448                         if (isnan(im->gdes[i].p_data[ii])
3449                             || (im->slopemode == 1
3450                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3451                             draw_on = 0;
3452                             continue;
3453                         }
3454                         if (draw_on == 0) {
3455                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3456                             if (im->slopemode == 0) {
3457                                 double    x = ii - 1 + im->xorigin;
3458                                 double    y = last_y;
3459
3460                                 gfx_line_fit(im, &x, &y);
3461                                 cairo_move_to(im->cr, x, y);
3462                                 x = ii + im->xorigin;
3463                                 y = last_y;
3464                                 gfx_line_fit(im, &x, &y);
3465                                 cairo_line_to(im->cr, x, y);
3466                             } else {
3467                                 double    x = ii - 1 + im->xorigin;
3468                                 double    y =
3469                                     ytr(im, im->gdes[i].p_data[ii - 1]);
3470                                 gfx_line_fit(im, &x, &y);
3471                                 cairo_move_to(im->cr, x, y);
3472                                 x = ii + im->xorigin;
3473                                 y = last_y;
3474                                 gfx_line_fit(im, &x, &y);
3475                                 cairo_line_to(im->cr, x, y);
3476                             }
3477                             draw_on = 1;
3478                         } else {
3479                             double    x1 = ii + im->xorigin;
3480                             double    y1 = ytr(im, im->gdes[i].p_data[ii]);
3481
3482                             if (im->slopemode == 0
3483                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3484                                 double    x = ii - 1 + im->xorigin;
3485                                 double    y = y1;
3486
3487                                 gfx_line_fit(im, &x, &y);
3488                                 cairo_line_to(im->cr, x, y);
3489                             };
3490                             last_y = y1;
3491                             gfx_line_fit(im, &x1, &y1);
3492                             cairo_line_to(im->cr, x1, y1);
3493                         };
3494                     }
3495                     cairo_set_source_rgba(im->cr,
3496                                           im->gdes[i].
3497                                           col.red,
3498                                           im->gdes[i].
3499                                           col.green,
3500                                           im->gdes[i].
3501                                           col.blue, im->gdes[i].col.alpha);
3502                     cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3503                     cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3504                     cairo_stroke(im->cr);
3505                     cairo_restore(im->cr);
3506                 } else {
3507                     int       idxI = -1;
3508                     double   *foreY =
3509                         (double *) malloc(sizeof(double) * im->xsize * 2);
3510                     double   *foreX =
3511                         (double *) malloc(sizeof(double) * im->xsize * 2);
3512                     double   *backY =
3513                         (double *) malloc(sizeof(double) * im->xsize * 2);
3514                     double   *backX =
3515                         (double *) malloc(sizeof(double) * im->xsize * 2);
3516                     int       drawem = 0;
3517
3518                     for (ii = 0; ii <= im->xsize; ii++) {
3519                         double    ybase, ytop;
3520
3521                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3522                             int       cntI = 1;
3523                             int       lastI = 0;
3524
3525                             while (cntI < idxI
3526                                    &&
3527                                    AlmostEqual2sComplement(foreY
3528                                                            [lastI],
3529                                                            foreY[cntI], 4)
3530                                    &&
3531                                    AlmostEqual2sComplement(foreY
3532                                                            [lastI],
3533                                                            foreY
3534                                                            [cntI + 1], 4)) {
3535                                 cntI++;
3536                             }
3537                             gfx_new_area(im,
3538                                          backX[0], backY[0],
3539                                          foreX[0], foreY[0],
3540                                          foreX[cntI],
3541                                          foreY[cntI], im->gdes[i].col);
3542                             while (cntI < idxI) {
3543                                 lastI = cntI;
3544                                 cntI++;
3545                                 while (cntI < idxI
3546                                        &&
3547                                        AlmostEqual2sComplement(foreY
3548                                                                [lastI],
3549                                                                foreY[cntI], 4)
3550                                        &&
3551                                        AlmostEqual2sComplement(foreY
3552                                                                [lastI],
3553                                                                foreY
3554                                                                [cntI
3555                                                                 + 1], 4)) {
3556                                     cntI++;
3557                                 }
3558                                 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3559                             }
3560                             gfx_add_point(im, backX[idxI], backY[idxI]);
3561                             while (idxI > 1) {
3562                                 lastI = idxI;
3563                                 idxI--;
3564                                 while (idxI > 1
3565                                        &&
3566                                        AlmostEqual2sComplement(backY
3567                                                                [lastI],
3568                                                                backY[idxI], 4)
3569                                        &&
3570                                        AlmostEqual2sComplement(backY
3571                                                                [lastI],
3572                                                                backY
3573                                                                [idxI
3574                                                                 - 1], 4)) {
3575                                     idxI--;
3576                                 }
3577                                 gfx_add_point(im, backX[idxI], backY[idxI]);
3578                             }
3579                             idxI = -1;
3580                             drawem = 0;
3581                             gfx_close_path(im);
3582                         }
3583                         if (drawem != 0) {
3584                             drawem = 0;
3585                             idxI = -1;
3586                         }
3587                         if (ii == im->xsize)
3588                             break;
3589                         if (im->slopemode == 0 && ii == 0) {
3590                             continue;
3591                         }
3592                         if (isnan(im->gdes[i].p_data[ii])) {
3593                             drawem = 1;
3594                             continue;
3595                         }
3596                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3597                         if (lastgdes && im->gdes[i].stack) {
3598                             ybase = ytr(im, lastgdes->p_data[ii]);
3599                         } else {
3600                             ybase = ytr(im, areazero);
3601                         }
3602                         if (ybase == ytop) {
3603                             drawem = 1;
3604                             continue;
3605                         }
3606
3607                         if (ybase > ytop) {
3608                             double    extra = ytop;
3609
3610                             ytop = ybase;
3611                             ybase = extra;
3612                         }
3613                         if (im->slopemode == 0) {
3614                             backY[++idxI] = ybase - 0.2;
3615                             backX[idxI] = ii + im->xorigin - 1;
3616                             foreY[idxI] = ytop + 0.2;
3617                             foreX[idxI] = ii + im->xorigin - 1;
3618                         }
3619                         backY[++idxI] = ybase - 0.2;
3620                         backX[idxI] = ii + im->xorigin;
3621                         foreY[idxI] = ytop + 0.2;
3622                         foreX[idxI] = ii + im->xorigin;
3623                     }
3624                     /* close up any remaining area */
3625                     free(foreY);
3626                     free(foreX);
3627                     free(backY);
3628                     free(backX);
3629                 }       /* else GF_LINE */
3630             }
3631             /* if color != 0x0 */
3632             /* make sure we do not run into trouble when stacking on NaN */
3633             for (ii = 0; ii < im->xsize; ii++) {
3634                 if (isnan(im->gdes[i].p_data[ii])) {
3635                     if (lastgdes && (im->gdes[i].stack)) {
3636                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3637                     } else {
3638                         im->gdes[i].p_data[ii] = areazero;
3639                     }
3640                 }
3641             }
3642             lastgdes = &(im->gdes[i]);
3643             break;
3644         case GF_STACK:
3645             rrd_set_error
3646                 ("STACK should already be turned into LINE or AREA here");
3647             return -1;
3648             break;
3649         }               /* switch */
3650     }
3651     cairo_reset_clip(im->cr);
3652
3653     /* grid_paint also does the text */
3654     if (!(im->extra_flags & ONLY_GRAPH))
3655         grid_paint(im);
3656     if (!(im->extra_flags & ONLY_GRAPH))
3657         axis_paint(im);
3658     /* the RULES are the last thing to paint ... */
3659     for (i = 0; i < im->gdes_c; i++) {
3660
3661         switch (im->gdes[i].gf) {
3662         case GF_HRULE:
3663             if (im->gdes[i].yrule >= im->minval
3664                 && im->gdes[i].yrule <= im->maxval) {
3665                 cairo_save(im->cr);
3666                 if (im->gdes[i].dash) {
3667                     cairo_set_dash(im->cr,
3668                                    im->gdes[i].p_dashes,
3669                                    im->gdes[i].ndash, im->gdes[i].offset);
3670                 }
3671                 gfx_line(im, im->xorigin,
3672                          ytr(im, im->gdes[i].yrule),
3673                          im->xorigin + im->xsize,
3674                          ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3675                 cairo_stroke(im->cr);
3676                 cairo_restore(im->cr);
3677             }
3678             break;
3679         case GF_VRULE:
3680             if (im->gdes[i].xrule >= im->start
3681                 && im->gdes[i].xrule <= im->end) {
3682                 cairo_save(im->cr);
3683                 if (im->gdes[i].dash) {
3684                     cairo_set_dash(im->cr,
3685                                    im->gdes[i].p_dashes,
3686                                    im->gdes[i].ndash, im->gdes[i].offset);
3687                 }
3688                 gfx_line(im,
3689                          xtr(im, im->gdes[i].xrule),
3690                          im->yorigin, xtr(im,
3691                                           im->
3692                                           gdes[i].
3693                                           xrule),
3694                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3695                 cairo_stroke(im->cr);
3696                 cairo_restore(im->cr);
3697             }
3698             break;
3699         default:
3700             break;
3701         }
3702     }
3703
3704
3705     switch (im->imgformat) {
3706     case IF_PNG:
3707     {
3708         cairo_status_t status;
3709
3710         status = strlen(im->graphfile) ?
3711             cairo_surface_write_to_png(im->surface, im->graphfile)
3712             : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3713                                                 im);
3714
3715         if (status != CAIRO_STATUS_SUCCESS) {
3716             rrd_set_error("Could not save png to '%s'", im->graphfile);
3717             return 1;
3718         }
3719         break;
3720     }
3721     default:
3722         if (strlen(im->graphfile)) {
3723             cairo_show_page(im->cr);
3724         } else {
3725             cairo_surface_finish(im->surface);
3726         }
3727         break;
3728     }
3729
3730     return 0;
3731 }
3732
3733
3734 /*****************************************************
3735  * graph stuff
3736  *****************************************************/
3737
3738 int gdes_alloc(
3739     image_desc_t *im)
3740 {
3741
3742     im->gdes_c++;
3743     if ((im->gdes = (graph_desc_t *)
3744          rrd_realloc(im->gdes, (im->gdes_c)
3745                      * sizeof(graph_desc_t))) == NULL) {
3746         rrd_set_error("realloc graph_descs");
3747         return -1;
3748     }
3749
3750
3751     im->gdes[im->gdes_c - 1].step = im->step;
3752     im->gdes[im->gdes_c - 1].step_orig = im->step;
3753     im->gdes[im->gdes_c - 1].stack = 0;
3754     im->gdes[im->gdes_c - 1].linewidth = 0;
3755     im->gdes[im->gdes_c - 1].debug = 0;
3756     im->gdes[im->gdes_c - 1].start = im->start;
3757     im->gdes[im->gdes_c - 1].start_orig = im->start;
3758     im->gdes[im->gdes_c - 1].end = im->end;
3759     im->gdes[im->gdes_c - 1].end_orig = im->end;
3760     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3761     im->gdes[im->gdes_c - 1].data = NULL;
3762     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3763     im->gdes[im->gdes_c - 1].data_first = 0;
3764     im->gdes[im->gdes_c - 1].p_data = NULL;
3765     im->gdes[im->gdes_c - 1].rpnp = NULL;
3766     im->gdes[im->gdes_c - 1].p_dashes = NULL;
3767     im->gdes[im->gdes_c - 1].shift = 0.0;
3768     im->gdes[im->gdes_c - 1].dash = 0;
3769     im->gdes[im->gdes_c - 1].ndash = 0;
3770     im->gdes[im->gdes_c - 1].offset = 0;
3771     im->gdes[im->gdes_c - 1].col.red = 0.0;
3772     im->gdes[im->gdes_c - 1].col.green = 0.0;
3773     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3774     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3775     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3776     im->gdes[im->gdes_c - 1].format[0] = '\0';
3777     im->gdes[im->gdes_c - 1].strftm = 0;
3778     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3779     im->gdes[im->gdes_c - 1].ds = -1;
3780     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3781     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3782     im->gdes[im->gdes_c - 1].yrule = DNAN;
3783     im->gdes[im->gdes_c - 1].xrule = 0;
3784     return 0;
3785 }
3786
3787 /* copies input untill the first unescaped colon is found
3788    or until input ends. backslashes have to be escaped as well */
3789 int scan_for_col(
3790     const char *const input,
3791     int len,
3792     char *const output)
3793 {
3794     int       inp, outp = 0;
3795
3796     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3797         if (input[inp] == '\\'
3798             && input[inp + 1] != '\0'
3799             && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3800             output[outp++] = input[++inp];
3801         } else {
3802             output[outp++] = input[inp];
3803         }
3804     }
3805     output[outp] = '\0';
3806     return inp;
3807 }
3808
3809 /* Now just a wrapper around rrd_graph_v */
3810 int rrd_graph(
3811     int argc,
3812     char **argv,
3813     char ***prdata,
3814     int *xsize,
3815     int *ysize,
3816     FILE * stream,
3817     double *ymin,
3818     double *ymax)
3819 {
3820     int       prlines = 0;
3821     rrd_info_t *grinfo = NULL;
3822     rrd_info_t *walker;
3823
3824     grinfo = rrd_graph_v(argc, argv);
3825     if (grinfo == NULL)
3826         return -1;
3827     walker = grinfo;
3828     (*prdata) = NULL;
3829     while (walker) {
3830         if (strcmp(walker->key, "image_info") == 0) {
3831             prlines++;
3832             if (((*prdata) =
3833                  (char**)rrd_realloc((*prdata),
3834                              (prlines + 1) * sizeof(char *))) == NULL) {
3835                 rrd_set_error("realloc prdata");
3836                 return 0;
3837             }
3838             /* imginfo goes to position 0 in the prdata array */
3839             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3840                                              + 2) * sizeof(char));
3841             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3842             (*prdata)[prlines] = NULL;
3843         }
3844         /* skip anything else */
3845         walker = walker->next;
3846     }
3847     walker = grinfo;
3848     *xsize = 0;
3849     *ysize = 0;
3850     *ymin = 0;
3851     *ymax = 0;
3852     while (walker) {
3853         if (strcmp(walker->key, "image_width") == 0) {
3854             *xsize = walker->value.u_cnt;
3855         } else if (strcmp(walker->key, "image_height") == 0) {
3856             *ysize = walker->value.u_cnt;
3857         } else if (strcmp(walker->key, "value_min") == 0) {
3858             *ymin = walker->value.u_val;
3859         } else if (strcmp(walker->key, "value_max") == 0) {
3860             *ymax = walker->value.u_val;
3861         } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3862             prlines++;
3863             if (((*prdata) =
3864                  (char**)rrd_realloc((*prdata),
3865                              (prlines + 1) * sizeof(char *))) == NULL) {
3866                 rrd_set_error("realloc prdata");
3867                 return 0;
3868             }
3869             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3870                                              + 2) * sizeof(char));
3871             (*prdata)[prlines] = NULL;
3872             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3873         } else if (strcmp(walker->key, "image") == 0) {
3874             if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3875                    (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3876                 rrd_set_error("writing image");
3877                 return 0;
3878             }
3879         }
3880         /* skip anything else */
3881         walker = walker->next;
3882     }
3883     rrd_info_free(grinfo);
3884     return 0;
3885 }
3886
3887
3888 /* Some surgery done on this function, it became ridiculously big.
3889 ** Things moved:
3890 ** - initializing     now in rrd_graph_init()
3891 ** - options parsing  now in rrd_graph_options()
3892 ** - script parsing   now in rrd_graph_script()
3893 */
3894 rrd_info_t *rrd_graph_v(
3895 &nbs