24b58d08bf48b9b8264789aa340aeb382a1c8bc9
[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 != 's' && prt_fctn != '\0' && prt_fctn != 'g') {
1734                 free(legspace);
1735                 rrd_set_error
1736                     ("Unknown control code at the end of '%s\\%c'",
1737                      im->gdes[i].legend, prt_fctn);
1738                 return -1;
1739             }
1740             /* \n -> \l */
1741             if (prt_fctn == 'n') {
1742                 prt_fctn = 'l';
1743             }
1744
1745             /* remove exess space from the end of the legend for \g */
1746             while (prt_fctn == 'g' &&
1747                    leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1748                 leg_cc--;
1749                 im->gdes[i].legend[leg_cc] = '\0';
1750             }
1751
1752             if (leg_cc != 0) {
1753
1754                 /* no interleg space if string ends in \g */
1755                 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1756                 if (fill > 0) {
1757                     fill += legspace[i];
1758                 }
1759                 fill +=
1760                     gfx_get_text_width(im,
1761                                        fill + border,
1762                                        im->
1763                                        text_prop
1764                                        [TEXT_PROP_LEGEND].
1765                                        font_desc,
1766                                        im->tabwidth, im->gdes[i].legend);
1767                 leg_c++;
1768             } else {
1769                 legspace[i] = 0;
1770             }
1771             /* who said there was a special tag ... ? */
1772             if (prt_fctn == 'g') {
1773                 prt_fctn = '\0';
1774             }
1775
1776             if (prt_fctn == '\0') {
1777                 if(calc_width && (fill > legendwidth)){
1778                     legendwidth = fill;
1779                 }
1780                 if (i == im->gdes_c - 1 || fill > legendwidth) {
1781                     /* just one legend item is left right or center */
1782                     switch (default_txtalign) {
1783                     case TXA_RIGHT:
1784                         prt_fctn = 'r';
1785                         break;
1786                     case TXA_CENTER:
1787                         prt_fctn = 'c';
1788                         break;
1789                     case TXA_JUSTIFIED:
1790                         prt_fctn = 'j';
1791                         break;
1792                     default:
1793                         prt_fctn = 'l';
1794                         break;
1795                     }
1796                 }
1797                 /* is it time to place the legends ? */
1798                 if (fill > legendwidth) {
1799                     if (leg_c > 1) {
1800                         /* go back one */
1801                         i--;
1802                         fill = fill_last;
1803                         leg_c--;
1804                     }
1805                 }
1806                 if (leg_c == 1 && prt_fctn == 'j') {
1807                     prt_fctn = 'l';
1808                 }
1809             }
1810
1811             if (prt_fctn != '\0') {
1812                 leg_x = border;
1813                 if (leg_c >= 2 && prt_fctn == 'j') {
1814                     glue = (double)(legendwidth - fill) / (double)(leg_c - 1);
1815                 } else {
1816                     glue = 0;
1817                 }
1818                 if (prt_fctn == 'c')
1819                     leg_x = (double)(legendwidth - fill) / 2.0;
1820                 if (prt_fctn == 'r')
1821                     leg_x = legendwidth - fill + border;
1822                 for (ii = mark; ii <= i; ii++) {
1823                     if (im->gdes[ii].legend[0] == '\0')
1824                         continue;   /* skip empty legends */
1825                     im->gdes[ii].leg_x = leg_x;
1826                     im->gdes[ii].leg_y = leg_y + border;
1827                     leg_x +=
1828                         (double)gfx_get_text_width(im, leg_x,
1829                                            im->
1830                                            text_prop
1831                                            [TEXT_PROP_LEGEND].
1832                                            font_desc,
1833                                            im->tabwidth, im->gdes[ii].legend)
1834                         +(double)legspace[ii]
1835                         + glue;
1836                 }
1837                 leg_y_prev = leg_y;
1838                 if (leg_x > border || prt_fctn == 's')
1839                     leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1840                 if (prt_fctn == 's')
1841                     leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1842
1843                 if(calc_width && (fill > legendwidth)){
1844                     legendwidth = fill;
1845                 }
1846                 fill = 0;
1847                 leg_c = 0;
1848                 mark = ii;
1849             }
1850
1851             if(calc_width){
1852                 strcpy(im->gdes[i].legend, saved_legend);
1853             }
1854         }
1855
1856         if(calc_width){
1857             im->legendwidth = legendwidth + 2 * border;
1858         }
1859         else{
1860             im->legendheight = leg_y + border * 0.6;
1861         }
1862         free(legspace);
1863     }
1864     return 0;
1865 }
1866
1867 /* create a grid on the graph. it determines what to do
1868    from the values of xsize, start and end */
1869
1870 /* the xaxis labels are determined from the number of seconds per pixel
1871    in the requested graph */
1872
1873 int calc_horizontal_grid(
1874     image_desc_t
1875     *im)
1876 {
1877     double    range;
1878     double    scaledrange;
1879     int       pixel, i;
1880     int       gridind = 0;
1881     int       decimals, fractionals;
1882
1883     im->ygrid_scale.labfact = 2;
1884     range = im->maxval - im->minval;
1885     scaledrange = range / im->magfact;
1886     /* does the scale of this graph make it impossible to put lines
1887        on it? If so, give up. */
1888     if (isnan(scaledrange)) {
1889         return 0;
1890     }
1891
1892     /* find grid spaceing */
1893     pixel = 1;
1894     if (isnan(im->ygridstep)) {
1895         if (im->extra_flags & ALTYGRID) {
1896             /* find the value with max number of digits. Get number of digits */
1897             decimals =
1898                 ceil(log10
1899                      (max(fabs(im->maxval), fabs(im->minval)) *
1900                       im->viewfactor / im->magfact));
1901             if (decimals <= 0)  /* everything is small. make place for zero */
1902                 decimals = 1;
1903             im->ygrid_scale.gridstep =
1904                 pow((double) 10,
1905                     floor(log10(range * im->viewfactor / im->magfact))) /
1906                 im->viewfactor * im->magfact;
1907             if (im->ygrid_scale.gridstep == 0)  /* range is one -> 0.1 is reasonable scale */
1908                 im->ygrid_scale.gridstep = 0.1;
1909             /* should have at least 5 lines but no more then 15 */
1910             if (range / im->ygrid_scale.gridstep < 5
1911                 && im->ygrid_scale.gridstep >= 30)
1912                 im->ygrid_scale.gridstep /= 10;
1913             if (range / im->ygrid_scale.gridstep > 15)
1914                 im->ygrid_scale.gridstep *= 10;
1915             if (range / im->ygrid_scale.gridstep > 5) {
1916                 im->ygrid_scale.labfact = 1;
1917                 if (range / im->ygrid_scale.gridstep > 8
1918                     || im->ygrid_scale.gridstep <
1919                     1.8 * im->text_prop[TEXT_PROP_AXIS].size)
1920                     im->ygrid_scale.labfact = 2;
1921             } else {
1922                 im->ygrid_scale.gridstep /= 5;
1923                 im->ygrid_scale.labfact = 5;
1924             }
1925             fractionals =
1926                 floor(log10
1927                       (im->ygrid_scale.gridstep *
1928                        (double) im->ygrid_scale.labfact * im->viewfactor /
1929                        im->magfact));
1930             if (fractionals < 0) {  /* small amplitude. */
1931                 int       len = decimals - fractionals + 1;
1932
1933                 if (im->unitslength < len + 2)
1934                     im->unitslength = len + 2;
1935                 sprintf(im->ygrid_scale.labfmt,
1936                         "%%%d.%df%s", len,
1937                         -fractionals, (im->symbol != ' ' ? " %c" : ""));
1938             } else {
1939                 int       len = decimals + 1;
1940
1941                 if (im->unitslength < len + 2)
1942                     im->unitslength = len + 2;
1943                 sprintf(im->ygrid_scale.labfmt,
1944                         "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1945             }
1946         } else {        /* classic rrd grid */
1947             for (i = 0; ylab[i].grid > 0; i++) {
1948                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1949                 gridind = i;
1950                 if (pixel >= 5)
1951                     break;
1952             }
1953
1954             for (i = 0; i < 4; i++) {
1955                 if (pixel * ylab[gridind].lfac[i] >=
1956                     1.8 * im->text_prop[TEXT_PROP_AXIS].size) {
1957                     im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1958                     break;
1959                 }
1960             }
1961
1962             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1963         }
1964     } else {
1965         im->ygrid_scale.gridstep = im->ygridstep;
1966         im->ygrid_scale.labfact = im->ylabfact;
1967     }
1968     return 1;
1969 }
1970
1971 int draw_horizontal_grid(
1972     image_desc_t
1973     *im)
1974 {
1975     int       i;
1976     double    scaledstep;
1977     char      graph_label[100];
1978     int       nlabels = 0;
1979     double    X0 = im->xorigin;
1980     double    X1 = im->xorigin + im->xsize;
1981     int       sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1982     int       egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1983     double    MaxY;
1984     double second_axis_magfact = 0;
1985     char *second_axis_symb = "";
1986
1987     scaledstep =
1988         im->ygrid_scale.gridstep /
1989         (double) im->magfact * (double) im->viewfactor;
1990     MaxY = scaledstep * (double) egrid;
1991     for (i = sgrid; i <= egrid; i++) {
1992         double    Y0 = ytr(im,
1993                            im->ygrid_scale.gridstep * i);
1994         double    YN = ytr(im,
1995                            im->ygrid_scale.gridstep * (i + 1));
1996
1997         if (floor(Y0 + 0.5) >=
1998             im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1999             /* Make sure at least 2 grid labels are shown, even if it doesn't agree
2000                with the chosen settings. Add a label if required by settings, or if
2001                there is only one label so far and the next grid line is out of bounds. */
2002             if (i % im->ygrid_scale.labfact == 0
2003                 || (nlabels == 1
2004                     && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
2005                 if (im->symbol == ' ') {
2006                     if (im->extra_flags & ALTYGRID) {
2007                         sprintf(graph_label,
2008                                 im->ygrid_scale.labfmt,
2009                                 scaledstep * (double) i);
2010                     } else {
2011                         if (MaxY < 10) {
2012                             sprintf(graph_label, "%4.1f",
2013                                     scaledstep * (double) i);
2014                         } else {
2015                             sprintf(graph_label, "%4.0f",
2016                                     scaledstep * (double) i);
2017                         }
2018                     }
2019                 } else {
2020                     char      sisym = (i == 0 ? ' ' : im->symbol);
2021
2022                     if (im->extra_flags & ALTYGRID) {
2023                         sprintf(graph_label,
2024                                 im->ygrid_scale.labfmt,
2025                                 scaledstep * (double) i, sisym);
2026                     } else {
2027                         if (MaxY < 10) {
2028                             sprintf(graph_label, "%4.1f %c",
2029                                     scaledstep * (double) i, sisym);
2030                         } else {
2031                             sprintf(graph_label, "%4.0f %c",
2032                                     scaledstep * (double) i, sisym);
2033                         }
2034                     }
2035                 }
2036                 nlabels++;
2037                 if (im->second_axis_scale != 0){
2038                         char graph_label_right[100];
2039                         double sval = im->ygrid_scale.gridstep*(double)i*im->second_axis_scale+im->second_axis_shift;
2040                         if (im->second_axis_format[0] == '\0'){
2041                             if (!second_axis_magfact){
2042                                 double dummy = im->ygrid_scale.gridstep*(double)(sgrid+egrid)/2.0*im->second_axis_scale+im->second_axis_shift;
2043                                 auto_scale(im,&dummy,&second_axis_symb,&second_axis_magfact);
2044                             }
2045                             sval /= second_axis_magfact;
2046
2047                             if(MaxY < 10) {
2048                                 sprintf(graph_label_right,"%5.1f %s",sval,second_axis_symb);
2049                             } else {
2050                                 sprintf(graph_label_right,"%5.0f %s",sval,second_axis_symb);
2051                             }
2052                         }
2053                         else {
2054                            sprintf(graph_label_right,im->second_axis_format,sval);
2055                         }
2056                         gfx_text ( im,
2057                                X1+7, Y0,
2058                                im->graph_col[GRC_FONT],
2059                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2060                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2061                                graph_label_right );
2062                 }
2063
2064                 gfx_text(im,
2065                          X0 -
2066                          im->
2067                          text_prop[TEXT_PROP_AXIS].
2068                          size, Y0,
2069                          im->graph_col[GRC_FONT],
2070                          im->
2071                          text_prop[TEXT_PROP_AXIS].
2072                          font_desc,
2073                          im->tabwidth, 0.0,
2074                          GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2075                 gfx_line(im, X0 - 2, Y0, X0, Y0,
2076                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2077                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2078                          MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2079                 gfx_dashed_line(im, X0 - 2, Y0,
2080                                 X1 + 2, Y0,
2081                                 MGRIDWIDTH,
2082                                 im->
2083                                 graph_col
2084                                 [GRC_MGRID],
2085                                 im->grid_dash_on, im->grid_dash_off);
2086             } else if (!(im->extra_flags & NOMINOR)) {
2087                 gfx_line(im,
2088                          X0 - 2, Y0,
2089                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2090                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2091                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2092                 gfx_dashed_line(im, X0 - 1, Y0,
2093                                 X1 + 1, Y0,
2094                                 GRIDWIDTH,
2095                                 im->
2096                                 graph_col[GRC_GRID],
2097                                 im->grid_dash_on, im->grid_dash_off);
2098             }
2099         }
2100     }
2101     return 1;
2102 }
2103
2104 /* this is frexp for base 10 */
2105 double    frexp10(
2106     double,
2107     double *);
2108 double frexp10(
2109     double x,
2110     double *e)
2111 {
2112     double    mnt;
2113     int       iexp;
2114
2115     iexp = floor(log((double)fabs(x)) / log((double)10));
2116     mnt = x / pow(10.0, iexp);
2117     if (mnt >= 10.0) {
2118         iexp++;
2119         mnt = x / pow(10.0, iexp);
2120     }
2121     *e = iexp;
2122     return mnt;
2123 }
2124
2125
2126 /* logaritmic horizontal grid */
2127 int horizontal_log_grid(
2128     image_desc_t
2129     *im)
2130 {
2131     double    yloglab[][10] = {
2132         {
2133          1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2134          0.0, 0.0, 0.0}, {
2135                           1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2136                           0.0, 0.0, 0.0}, {
2137                                            1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2138                                            0.0, 0.0, 0.0}, {
2139                                                             1.0, 2.0, 4.0,
2140                                                             6.0, 8.0, 10.,
2141                                                             0.0,
2142                                                             0.0, 0.0, 0.0}, {
2143                                                                              1.0,
2144                                                                              2.0,
2145                                                                              3.0,
2146                                                                              4.0,
2147                                                                              5.0,
2148                                                                              6.0,
2149                                                                              7.0,
2150                                                                              8.0,
2151                                                                              9.0,
2152                                                                              10.},
2153         {
2154          0, 0, 0, 0, 0, 0, 0, 0, 0, 0}  /* last line */
2155     };
2156     int       i, j, val_exp, min_exp;
2157     double    nex;      /* number of decades in data */
2158     double    logscale; /* scale in logarithmic space */
2159     int       exfrac = 1;   /* decade spacing */
2160     int       mid = -1; /* row in yloglab for major grid */
2161     double    mspac;    /* smallest major grid spacing (pixels) */
2162     int       flab;     /* first value in yloglab to use */
2163     double    value, tmp, pre_value;
2164     double    X0, X1, Y0;
2165     char      graph_label[100];
2166
2167     nex = log10(im->maxval / im->minval);
2168     logscale = im->ysize / nex;
2169     /* major spacing for data with high dynamic range */
2170     while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2171         if (exfrac == 1)
2172             exfrac = 3;
2173         else
2174             exfrac += 3;
2175     }
2176
2177     /* major spacing for less dynamic data */
2178     do {
2179         /* search best row in yloglab */
2180         mid++;
2181         for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2182         mspac = logscale * log10(10.0 / yloglab[mid][i]);
2183     }
2184     while (mspac >
2185            2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2186     if (mid)
2187         mid--;
2188     /* find first value in yloglab */
2189     for (flab = 0;
2190          yloglab[mid][flab] < 10
2191          && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2192     if (yloglab[mid][flab] == 10.0) {
2193         tmp += 1.0;
2194         flab = 0;
2195     }
2196     val_exp = tmp;
2197     if (val_exp % exfrac)
2198         val_exp += abs(-val_exp % exfrac);
2199     X0 = im->xorigin;
2200     X1 = im->xorigin + im->xsize;
2201     /* draw grid */
2202     pre_value = DNAN;
2203     while (1) {
2204
2205         value = yloglab[mid][flab] * pow(10.0, val_exp);
2206         if (AlmostEqual2sComplement(value, pre_value, 4))
2207             break;      /* it seems we are not converging */
2208         pre_value = value;
2209         Y0 = ytr(im, value);
2210         if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2211             break;
2212         /* major grid line */
2213         gfx_line(im,
2214                  X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2215         gfx_line(im, X1, Y0, X1 + 2, Y0,
2216                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2217         gfx_dashed_line(im, X0 - 2, Y0,
2218                         X1 + 2, Y0,
2219                         MGRIDWIDTH,
2220                         im->
2221                         graph_col
2222                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2223         /* label */
2224         if (im->extra_flags & FORCE_UNITS_SI) {
2225             int       scale;
2226             double    pvalue;
2227             char      symbol;
2228
2229             scale = floor(val_exp / 3.0);
2230             if (value >= 1.0)
2231                 pvalue = pow(10.0, val_exp % 3);
2232             else
2233                 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2234             pvalue *= yloglab[mid][flab];
2235             if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2236                 && ((scale + si_symbcenter) >= 0))
2237                 symbol = si_symbol[scale + si_symbcenter];
2238             else
2239                 symbol = '?';
2240             sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2241         } else {
2242             sprintf(graph_label, "%3.0e", value);
2243         }
2244         if (im->second_axis_scale != 0){
2245                 char graph_label_right[100];
2246                 double sval = value*im->second_axis_scale+im->second_axis_shift;
2247                 if (im->second_axis_format[0] == '\0'){
2248                         if (im->extra_flags & FORCE_UNITS_SI) {
2249                                 double mfac = 1;
2250                                 char   *symb = "";
2251                                 auto_scale(im,&sval,&symb,&mfac);
2252                                 sprintf(graph_label_right,"%4.0f %s", sval,symb);
2253                         }
2254                         else {
2255                                 sprintf(graph_label_right,"%3.0e", sval);
2256                         }
2257                 }
2258                 else {
2259                       sprintf(graph_label_right,im->second_axis_format,sval);
2260                 }
2261
2262                 gfx_text ( im,
2263                                X1+7, Y0,
2264                                im->graph_col[GRC_FONT],
2265                                im->text_prop[TEXT_PROP_AXIS].font_desc,
2266                                im->tabwidth,0.0, GFX_H_LEFT, GFX_V_CENTER,
2267                                graph_label_right );
2268         }
2269
2270         gfx_text(im,
2271                  X0 -
2272                  im->
2273                  text_prop[TEXT_PROP_AXIS].
2274                  size, Y0,
2275                  im->graph_col[GRC_FONT],
2276                  im->
2277                  text_prop[TEXT_PROP_AXIS].
2278                  font_desc,
2279                  im->tabwidth, 0.0,
2280                  GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2281         /* minor grid */
2282         if (mid < 4 && exfrac == 1) {
2283             /* find first and last minor line behind current major line
2284              * i is the first line and j tha last */
2285             if (flab == 0) {
2286                 min_exp = val_exp - 1;
2287                 for (i = 1; yloglab[mid][i] < 10.0; i++);
2288                 i = yloglab[mid][i - 1] + 1;
2289                 j = 10;
2290             } else {
2291                 min_exp = val_exp;
2292                 i = yloglab[mid][flab - 1] + 1;
2293                 j = yloglab[mid][flab];
2294             }
2295
2296             /* draw minor lines below current major line */
2297             for (; i < j; i++) {
2298
2299                 value = i * pow(10.0, min_exp);
2300                 if (value < im->minval)
2301                     continue;
2302                 Y0 = ytr(im, value);
2303                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2304                     break;
2305                 /* draw lines */
2306                 gfx_line(im,
2307                          X0 - 2, Y0,
2308                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2309                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2310                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2311                 gfx_dashed_line(im, X0 - 1, Y0,
2312                                 X1 + 1, Y0,
2313                                 GRIDWIDTH,
2314                                 im->
2315                                 graph_col[GRC_GRID],
2316                                 im->grid_dash_on, im->grid_dash_off);
2317             }
2318         } else if (exfrac > 1) {
2319             for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2320                 value = pow(10.0, i);
2321                 if (value < im->minval)
2322                     continue;
2323                 Y0 = ytr(im, value);
2324                 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2325                     break;
2326                 /* draw lines */
2327                 gfx_line(im,
2328                          X0 - 2, Y0,
2329                          X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2330                 gfx_line(im, X1, Y0, X1 + 2, Y0,
2331                          GRIDWIDTH, im->graph_col[GRC_GRID]);
2332                 gfx_dashed_line(im, X0 - 1, Y0,
2333                                 X1 + 1, Y0,
2334                                 GRIDWIDTH,
2335                                 im->
2336                                 graph_col[GRC_GRID],
2337                                 im->grid_dash_on, im->grid_dash_off);
2338             }
2339         }
2340
2341         /* next decade */
2342         if (yloglab[mid][++flab] == 10.0) {
2343             flab = 0;
2344             val_exp += exfrac;
2345         }
2346     }
2347
2348     /* draw minor lines after highest major line */
2349     if (mid < 4 && exfrac == 1) {
2350         /* find first and last minor line below current major line
2351          * i is the first line and j tha last */
2352         if (flab == 0) {
2353             min_exp = val_exp - 1;
2354             for (i = 1; yloglab[mid][i] < 10.0; i++);
2355             i = yloglab[mid][i - 1] + 1;
2356             j = 10;
2357         } else {
2358             min_exp = val_exp;
2359             i = yloglab[mid][flab - 1] + 1;
2360             j = yloglab[mid][flab];
2361         }
2362
2363         /* draw minor lines below current major line */
2364         for (; i < j; i++) {
2365
2366             value = i * pow(10.0, min_exp);
2367             if (value < im->minval)
2368                 continue;
2369             Y0 = ytr(im, value);
2370             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2371                 break;
2372             /* draw lines */
2373             gfx_line(im,
2374                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2375             gfx_line(im, X1, Y0, X1 + 2, Y0,
2376                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2377             gfx_dashed_line(im, X0 - 1, Y0,
2378                             X1 + 1, Y0,
2379                             GRIDWIDTH,
2380                             im->
2381                             graph_col[GRC_GRID],
2382                             im->grid_dash_on, im->grid_dash_off);
2383         }
2384     }
2385     /* fancy minor gridlines */
2386     else if (exfrac > 1) {
2387         for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2388             value = pow(10.0, i);
2389             if (value < im->minval)
2390                 continue;
2391             Y0 = ytr(im, value);
2392             if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2393                 break;
2394             /* draw lines */
2395             gfx_line(im,
2396                      X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2397             gfx_line(im, X1, Y0, X1 + 2, Y0,
2398                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2399             gfx_dashed_line(im, X0 - 1, Y0,
2400                             X1 + 1, Y0,
2401                             GRIDWIDTH,
2402                             im->
2403                             graph_col[GRC_GRID],
2404                             im->grid_dash_on, im->grid_dash_off);
2405         }
2406     }
2407
2408     return 1;
2409 }
2410
2411
2412 void vertical_grid(
2413     image_desc_t *im)
2414 {
2415     int       xlab_sel; /* which sort of label and grid ? */
2416     time_t    ti, tilab, timajor;
2417     long      factor;
2418     char      graph_label[100];
2419     double    X0, Y0, Y1;   /* points for filled graph and more */
2420     struct tm tm;
2421
2422     /* the type of time grid is determined by finding
2423        the number of seconds per pixel in the graph */
2424     if (im->xlab_user.minsec == -1) {
2425         factor = (im->end - im->start) / im->xsize;
2426         xlab_sel = 0;
2427         while (xlab[xlab_sel + 1].minsec !=
2428                -1 && xlab[xlab_sel + 1].minsec <= factor) {
2429             xlab_sel++;
2430         }               /* pick the last one */
2431         while (xlab[xlab_sel - 1].minsec ==
2432                xlab[xlab_sel].minsec
2433                && xlab[xlab_sel].length > (im->end - im->start)) {
2434             xlab_sel--;
2435         }               /* go back to the smallest size */
2436         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2437         im->xlab_user.gridst = xlab[xlab_sel].gridst;
2438         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2439         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2440         im->xlab_user.labtm = xlab[xlab_sel].labtm;
2441         im->xlab_user.labst = xlab[xlab_sel].labst;
2442         im->xlab_user.precis = xlab[xlab_sel].precis;
2443         im->xlab_user.stst = xlab[xlab_sel].stst;
2444     }
2445
2446     /* y coords are the same for every line ... */
2447     Y0 = im->yorigin;
2448     Y1 = im->yorigin - im->ysize;
2449     /* paint the minor grid */
2450     if (!(im->extra_flags & NOMINOR)) {
2451         for (ti = find_first_time(im->start,
2452                                   im->
2453                                   xlab_user.
2454                                   gridtm,
2455                                   im->
2456                                   xlab_user.
2457                                   gridst),
2458              timajor =
2459              find_first_time(im->start,
2460                              im->xlab_user.
2461                              mgridtm,
2462                              im->xlab_user.
2463                              mgridst);
2464              ti < im->end;
2465              ti =
2466              find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2467             ) {
2468             /* are we inside the graph ? */
2469             if (ti < im->start || ti > im->end)
2470                 continue;
2471             while (timajor < ti) {
2472                 timajor = find_next_time(timajor,
2473                                          im->
2474                                          xlab_user.
2475                                          mgridtm, im->xlab_user.mgridst);
2476             }
2477             if (ti == timajor)
2478                 continue;   /* skip as falls on major grid line */
2479             X0 = xtr(im, ti);
2480             gfx_line(im, X0, Y1 - 2, X0, Y1,
2481                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2482             gfx_line(im, X0, Y0, X0, Y0 + 2,
2483                      GRIDWIDTH, im->graph_col[GRC_GRID]);
2484             gfx_dashed_line(im, X0, Y0 + 1, X0,
2485                             Y1 - 1, GRIDWIDTH,
2486                             im->
2487                             graph_col[GRC_GRID],
2488                             im->grid_dash_on, im->grid_dash_off);
2489         }
2490     }
2491
2492     /* paint the major grid */
2493     for (ti = find_first_time(im->start,
2494                               im->
2495                               xlab_user.
2496                               mgridtm,
2497                               im->
2498                               xlab_user.
2499                               mgridst);
2500          ti < im->end;
2501          ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2502         ) {
2503         /* are we inside the graph ? */
2504         if (ti < im->start || ti > im->end)
2505             continue;
2506         X0 = xtr(im, ti);
2507         gfx_line(im, X0, Y1 - 2, X0, Y1,
2508                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2509         gfx_line(im, X0, Y0, X0, Y0 + 3,
2510                  MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2511         gfx_dashed_line(im, X0, Y0 + 3, X0,
2512                         Y1 - 2, MGRIDWIDTH,
2513                         im->
2514                         graph_col
2515                         [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2516     }
2517     /* paint the labels below the graph */
2518     for (ti =
2519          find_first_time(im->start -
2520                          im->xlab_user.
2521                          precis / 2,
2522                          im->xlab_user.
2523                          labtm,
2524                          im->xlab_user.
2525                          labst);
2526          ti <=
2527          im->end -
2528          im->xlab_user.precis / 2;
2529          ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2530         ) {
2531         tilab = ti + im->xlab_user.precis / 2;  /* correct time for the label */
2532         /* are we inside the graph ? */
2533         if (tilab < im->start || tilab > im->end)
2534             continue;
2535 #if HAVE_STRFTIME
2536         localtime_r(&tilab, &tm);
2537         strftime(graph_label, 99, im->xlab_user.stst, &tm);
2538 #else
2539 # error "your libc has no strftime I guess we'll abort the exercise here."
2540 #endif
2541         gfx_text(im,
2542                  xtr(im, tilab),
2543                  Y0 + 3,
2544                  im->graph_col[GRC_FONT],
2545                  im->
2546                  text_prop[TEXT_PROP_AXIS].
2547                  font_desc,
2548                  im->tabwidth, 0.0,
2549                  GFX_H_CENTER, GFX_V_TOP, graph_label);
2550     }
2551
2552 }
2553
2554
2555 void axis_paint(
2556     image_desc_t *im)
2557 {
2558     /* draw x and y axis */
2559     /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2560        im->xorigin+im->xsize,im->yorigin-im->ysize,
2561        GRIDWIDTH, im->graph_col[GRC_AXIS]);
2562
2563        gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2564        im->xorigin+im->xsize,im->yorigin-im->ysize,
2565        GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2566
2567     gfx_line(im, im->xorigin - 4,
2568              im->yorigin,
2569              im->xorigin + im->xsize +
2570              4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2571     gfx_line(im, im->xorigin,
2572              im->yorigin + 4,
2573              im->xorigin,
2574              im->yorigin - im->ysize -
2575              4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2576     /* arrow for X and Y axis direction */
2577     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 */
2578                  im->graph_col[GRC_ARROW]);
2579     gfx_close_path(im);
2580     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 */
2581                  im->graph_col[GRC_ARROW]);
2582     gfx_close_path(im);
2583     if (im->second_axis_scale != 0){
2584        gfx_line ( im, im->xorigin+im->xsize,im->yorigin+4,
2585                          im->xorigin+im->xsize,im->yorigin-im->ysize-4,
2586                          MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2587        gfx_new_area ( im,
2588                    im->xorigin+im->xsize-2,  im->yorigin-im->ysize-2,
2589                    im->xorigin+im->xsize+3,  im->yorigin-im->ysize-2,
2590                    im->xorigin+im->xsize,    im->yorigin-im->ysize-7, /* LINEOFFSET */
2591                    im->graph_col[GRC_ARROW]);
2592        gfx_close_path(im);
2593     }
2594
2595 }
2596
2597 void grid_paint(
2598     image_desc_t *im)
2599 {
2600     long      i;
2601     int       res = 0;
2602     double    X0, Y0;   /* points for filled graph and more */
2603     struct gfx_color_t water_color;
2604
2605     if (im->draw_3d_border > 0) {
2606             /* draw 3d border */
2607             i = im->draw_3d_border;
2608             gfx_new_area(im, 0, im->yimg,
2609                          i, im->yimg - i, i, i, im->graph_col[GRC_SHADEA]);
2610             gfx_add_point(im, im->ximg - i, i);
2611             gfx_add_point(im, im->ximg, 0);
2612             gfx_add_point(im, 0, 0);
2613             gfx_close_path(im);
2614             gfx_new_area(im, i, im->yimg - i,
2615                          im->ximg - i,
2616                          im->yimg - i, im->ximg - i, i, im->graph_col[GRC_SHADEB]);
2617             gfx_add_point(im, im->ximg, 0);
2618             gfx_add_point(im, im->ximg, im->yimg);
2619             gfx_add_point(im, 0, im->yimg);
2620             gfx_close_path(im);
2621     }
2622     if (im->draw_x_grid == 1)
2623         vertical_grid(im);
2624     if (im->draw_y_grid == 1) {
2625         if (im->logarithmic) {
2626             res = horizontal_log_grid(im);
2627         } else {
2628             res = draw_horizontal_grid(im);
2629         }
2630
2631         /* dont draw horizontal grid if there is no min and max val */
2632         if (!res) {
2633             char     *nodata = "No Data found";
2634
2635             gfx_text(im, im->ximg / 2,
2636                      (2 * im->yorigin -
2637                       im->ysize) / 2,
2638                      im->graph_col[GRC_FONT],
2639                      im->
2640                      text_prop[TEXT_PROP_AXIS].
2641                      font_desc,
2642                      im->tabwidth, 0.0,
2643                      GFX_H_CENTER, GFX_V_CENTER, nodata);
2644         }
2645     }
2646
2647     /* yaxis unit description */
2648     if (im->ylegend[0] != '\0'){
2649         gfx_text(im,
2650                  im->xOriginLegendY+10,
2651                  im->yOriginLegendY,
2652                  im->graph_col[GRC_FONT],
2653                  im->
2654                  text_prop[TEXT_PROP_UNIT].
2655                  font_desc,
2656                  im->tabwidth,
2657                  RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2658
2659     }
2660     if (im->second_axis_legend[0] != '\0'){
2661             gfx_text( im,
2662                   im->xOriginLegendY2+10,
2663                   im->yOriginLegendY2,
2664                   im->graph_col[GRC_FONT],
2665                   im->text_prop[TEXT_PROP_UNIT].font_desc,
2666                   im->tabwidth,
2667                   RRDGRAPH_YLEGEND_ANGLE,
2668                   GFX_H_CENTER, GFX_V_CENTER,
2669                   im->second_axis_legend);
2670     }
2671
2672     /* graph title */
2673     gfx_text(im,
2674              im->xOriginTitle, im->yOriginTitle+6,
2675              im->graph_col[GRC_FONT],
2676              im->
2677              text_prop[TEXT_PROP_TITLE].
2678              font_desc,
2679              im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2680     /* rrdtool 'logo' */
2681     if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2682         water_color = im->graph_col[GRC_FONT];
2683         water_color.alpha = 0.3;
2684         double xpos = im->legendposition == EAST ? im->xOriginLegendY : im->ximg - 4;
2685         gfx_text(im, xpos, 5,
2686                  water_color,
2687                  im->
2688                  text_prop[TEXT_PROP_WATERMARK].
2689                  font_desc, im->tabwidth,
2690                  -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2691     }
2692     /* graph watermark */
2693     if (im->watermark[0] != '\0') {
2694         water_color = im->graph_col[GRC_FONT];
2695         water_color.alpha = 0.3;
2696         gfx_text(im,
2697                  im->ximg / 2, im->yimg - 6,
2698                  water_color,
2699                  im->
2700                  text_prop[TEXT_PROP_WATERMARK].
2701                  font_desc, im->tabwidth, 0,
2702                  GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2703     }
2704
2705     /* graph labels */
2706     if (!(im->extra_flags & NOLEGEND) && !(im->extra_flags & ONLY_GRAPH)) {
2707         for (i = 0; i < im->gdes_c; i++) {
2708             if (im->gdes[i].legend[0] == '\0')
2709                 continue;
2710             /* im->gdes[i].leg_y is the bottom of the legend */
2711             X0 = im->xOriginLegend + im->gdes[i].leg_x;
2712             Y0 = im->legenddirection == TOP_DOWN ? im->yOriginLegend + im->gdes[i].leg_y : im->yOriginLegend + im->legendheight - im->gdes[i].leg_y;
2713             gfx_text(im, X0, Y0,
2714                      im->graph_col[GRC_FONT],
2715                      im->
2716                      text_prop
2717                      [TEXT_PROP_LEGEND].font_desc,
2718                      im->tabwidth, 0.0,
2719                      GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2720             /* The legend for GRAPH items starts with "M " to have
2721                enough space for the box */
2722             if (im->gdes[i].gf != GF_PRINT &&
2723                 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2724                 double    boxH, boxV;
2725                 double    X1, Y1;
2726
2727                 boxH = gfx_get_text_width(im, 0,
2728                                           im->
2729                                           text_prop
2730                                           [TEXT_PROP_LEGEND].
2731                                           font_desc,
2732                                           im->tabwidth, "o") * 1.2;
2733                 boxV = boxH;
2734                 /* shift the box up a bit */
2735                 Y0 -= boxV * 0.4;
2736
2737         if (im->dynamic_labels && im->gdes[i].gf == GF_HRULE) { /* [-] */ 
2738                         cairo_save(im->cr);
2739                         cairo_new_path(im->cr);
2740                         cairo_set_line_width(im->cr, 1.0);
2741                         gfx_line(im,
2742                                 X0, Y0 - boxV / 2,
2743                                 X0 + boxH, Y0 - boxV / 2,
2744                                 1.0, im->gdes[i].col);
2745                         gfx_close_path(im);
2746                 } else if (im->dynamic_labels && im->gdes[i].gf == GF_VRULE) { /* [|] */
2747                         cairo_save(im->cr);
2748                         cairo_new_path(im->cr);
2749                         cairo_set_line_width(im->cr, 1.0);
2750                         gfx_line(im,
2751                                 X0 + boxH / 2, Y0,
2752                                 X0 + boxH / 2, Y0 - boxV,
2753                                 1.0, im->gdes[i].col);
2754                         gfx_close_path(im);
2755                 } else if (im->dynamic_labels && im->gdes[i].gf == GF_LINE) { /* [/] */
2756                         cairo_save(im->cr);
2757                         cairo_new_path(im->cr);
2758                         cairo_set_line_width(im->cr, im->gdes[i].linewidth);
2759                         gfx_line(im,
2760                                 X0, Y0,
2761                                 X0 + boxH, Y0 - boxV,
2762                                 im->gdes[i].linewidth, im->gdes[i].col);
2763                         gfx_close_path(im);
2764                 } else {
2765                 /* make sure transparent colors show up the same way as in the graph */
2766                         gfx_new_area(im,
2767                                      X0, Y0 - boxV,
2768                                      X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2769                         gfx_add_point(im, X0 + boxH, Y0 - boxV);
2770                         gfx_close_path(im);
2771                         gfx_new_area(im, X0, Y0 - boxV, X0,
2772                                      Y0, X0 + boxH, Y0, im->gdes[i].col);
2773                         gfx_add_point(im, X0 + boxH, Y0 - boxV);
2774                         gfx_close_path(im);
2775                         cairo_save(im->cr);
2776                         cairo_new_path(im->cr);
2777                         cairo_set_line_width(im->cr, 1.0);
2778                         X1 = X0 + boxH;
2779                         Y1 = Y0 - boxV;
2780                         gfx_line_fit(im, &X0, &Y0);
2781                         gfx_line_fit(im, &X1, &Y1);
2782                         cairo_move_to(im->cr, X0, Y0);
2783                         cairo_line_to(im->cr, X1, Y0);
2784                         cairo_line_to(im->cr, X1, Y1);
2785                         cairo_line_to(im->cr, X0, Y1);
2786                         cairo_close_path(im->cr);
2787                         cairo_set_source_rgba(im->cr,
2788                                               im->graph_col[GRC_FRAME].red,
2789                                               im->graph_col[GRC_FRAME].green,
2790                                               im->graph_col[GRC_FRAME].blue,
2791                                               im->graph_col[GRC_FRAME].alpha);
2792                 }
2793                 if (im->gdes[i].dash) {
2794                     /* make box borders in legend dashed if the graph is dashed */
2795                     double    dashes[] = {
2796                         3.0
2797                     };
2798                     cairo_set_dash(im->cr, dashes, 1, 0.0);
2799                 }
2800                 cairo_stroke(im->cr);
2801                 cairo_restore(im->cr);
2802             }
2803         }
2804     }
2805 }
2806
2807
2808 /*****************************************************
2809  * lazy check make sure we rely need to create this graph
2810  *****************************************************/
2811
2812 int lazy_check(
2813     image_desc_t *im)
2814 {
2815     FILE     *fd = NULL;
2816     int       size = 1;
2817     struct stat imgstat;
2818
2819     if (im->lazy == 0)
2820         return 0;       /* no lazy option */
2821     if (strlen(im->graphfile) == 0)
2822         return 0;       /* inmemory option */
2823     if (stat(im->graphfile, &imgstat) != 0)
2824         return 0;       /* can't stat */
2825     /* one pixel in the existing graph is more then what we would
2826        change here ... */
2827     if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2828         return 0;
2829     if ((fd = fopen(im->graphfile, "rb")) == NULL)
2830         return 0;       /* the file does not exist */
2831     switch (im->imgformat) {
2832     case IF_PNG:
2833         size = PngSize(fd, &(im->ximg), &(im->yimg));
2834         break;
2835     default:
2836         size = 1;
2837     }
2838     fclose(fd);
2839     return size;
2840 }
2841
2842
2843 int graph_size_location(
2844     image_desc_t
2845     *im,
2846     int elements)
2847 {
2848     /* The actual size of the image to draw is determined from
2849      ** several sources.  The size given on the command line is
2850      ** the graph area but we need more as we have to draw labels
2851      ** and other things outside the graph area. If the option
2852      ** --full-size-mode is selected the size defines the total
2853      ** image size and the size available for the graph is
2854      ** calculated.
2855      */
2856
2857     /** +---+-----------------------------------+
2858      ** | y |...............graph title.........|
2859      ** |   +---+-------------------------------+
2860      ** | a | y |                               |
2861      ** | x |   |                               |
2862      ** | i | a |                               |
2863      ** | s | x |       main graph area         |
2864      ** |   | i |                               |
2865      ** | t | s |                               |
2866      ** | i |   |                               |
2867      ** | t | l |                               |
2868      ** | l | b +-------------------------------+
2869      ** | e | l |       x axis labels           |
2870      ** +---+---+-------------------------------+
2871      ** |....................legends............|
2872      ** +---------------------------------------+
2873      ** |                   watermark           |
2874      ** +---------------------------------------+
2875      */
2876
2877     int       Xvertical = 0, Xvertical2 = 0, Ytitle =
2878         0, Xylabel = 0, Xmain = 0, Ymain =
2879         0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2880
2881     // no legends and no the shall be plotted it's easy
2882     if (im->extra_flags & ONLY_GRAPH) {
2883         im->xorigin = 0;
2884         im->ximg = im->xsize;
2885         im->yimg = im->ysize;
2886         im->yorigin = im->ysize;
2887         ytr(im, DNAN);
2888         return 0;
2889     }
2890
2891     if(im->watermark[0] != '\0') {
2892         Ywatermark = im->text_prop[TEXT_PROP_WATERMARK].size * 2;
2893     }
2894
2895     // calculate the width of the left vertical legend
2896     if (im->ylegend[0] != '\0') {
2897         Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2898     }
2899
2900     // calculate the width of the right vertical legend
2901     if (im->second_axis_legend[0] != '\0') {
2902         Xvertical2 = im->text_prop[TEXT_PROP_UNIT].size * 2;
2903     }
2904     else{
2905         Xvertical2 = Xspacing;
2906     }
2907
2908     if (im->title[0] != '\0') {
2909         /* The title is placed "inbetween" two text lines so it
2910          ** automatically has some vertical spacing.  The horizontal
2911          ** spacing is added here, on each side.
2912          */
2913         /* if necessary, reduce the font size of the title until it fits the image width */
2914         Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2915     }
2916     else{
2917         // we have no title; get a little clearing from the top
2918         Ytitle = 1.5 * Yspacing;
2919     }
2920
2921     if (elements) {
2922         if (im->draw_x_grid) {
2923             // calculate the height of the horizontal labelling
2924             Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2925         }
2926         if (im->draw_y_grid || im->forceleftspace) {
2927             // calculate the width of the vertical labelling
2928             Xylabel =
2929                 gfx_get_text_width(im, 0,
2930                                    im->text_prop[TEXT_PROP_AXIS].font_desc,
2931                                    im->tabwidth, "0") * im->unitslength;
2932         }
2933     }
2934
2935     // add some space to the labelling
2936     Xylabel += Xspacing;
2937
2938     /* If the legend is printed besides the graph the width has to be
2939      ** calculated first. Placing the legend north or south of the
2940      ** graph requires the width calculation first, so the legend is
2941      ** skipped for the moment.
2942      */
2943     im->legendheight = 0;
2944     im->legendwidth = 0;
2945     if (!(im->extra_flags & NOLEGEND)) {
2946         if(im->legendposition == WEST || im->legendposition == EAST){
2947             if (leg_place(im, 1) == -1){
2948                 return -1;
2949             }
2950         }
2951     }
2952
2953     if (im->extra_flags & FULL_SIZE_MODE) {
2954
2955         /* The actual size of the image to draw has been determined by the user.
2956          ** The graph area is the space remaining after accounting for the legend,
2957          ** the watermark, the axis labels, and the title.
2958          */
2959         im->ximg = im->xsize;
2960         im->yimg = im->ysize;
2961         Xmain = im->ximg;
2962         Ymain = im->yimg;
2963
2964         /* Now calculate the total size.  Insert some spacing where
2965            desired.  im->xorigin and im->yorigin need to correspond
2966            with the lower left corner of the main graph area or, if
2967            this one is not set, the imaginary box surrounding the
2968            pie chart area. */
2969         /* Initial size calculation for the main graph area */
2970
2971         Xmain -= Xylabel;// + Xspacing;
2972         if((im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
2973             Xmain -= im->legendwidth;// + Xspacing;
2974         }
2975         if (im->second_axis_scale != 0){
2976             Xmain -= Xylabel;
2977         }
2978         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
2979             Xmain -= Xspacing;
2980         }
2981
2982         Xmain -= Xvertical + Xvertical2;
2983
2984         /* limit the remaining space to 0 */
2985         if(Xmain < 1){
2986             Xmain = 1;
2987         }
2988         im->xsize = Xmain;
2989
2990         /* Putting the legend north or south, the height can now be calculated */
2991         if (!(im->extra_flags & NOLEGEND)) {
2992             if(im->legendposition == NORTH || im->legendposition == SOUTH){
2993                 im->legendwidth = im->ximg;
2994                 if (leg_place(im, 0) == -1){
2995                     return -1;
2996                 }
2997             }
2998         }
2999
3000         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3001             Ymain -=  Yxlabel + im->legendheight;
3002         }
3003         else{
3004             Ymain -= Yxlabel;
3005         }
3006
3007         /* reserve space for the title *or* some padding above the graph */
3008         Ymain -= Ytitle;
3009
3010             /* reserve space for padding below the graph */
3011         if (im->extra_flags & NOLEGEND) {
3012             Ymain -= Yspacing;
3013         }
3014
3015         if (im->watermark[0] != '\0') {
3016             Ymain -= Ywatermark;
3017         }
3018         /* limit the remaining height to 0 */
3019         if(Ymain < 1){
3020             Ymain = 1;
3021         }
3022         im->ysize = Ymain;
3023     } else {            /* dimension options -width and -height refer to the dimensions of the main graph area */
3024
3025         /* The actual size of the image to draw is determined from
3026          ** several sources.  The size given on the command line is
3027          ** the graph area but we need more as we have to draw labels
3028          ** and other things outside the graph area.
3029          */
3030
3031         if (elements) {
3032             Xmain = im->xsize; // + Xspacing;
3033             Ymain = im->ysize;
3034         }
3035
3036         im->ximg = Xmain + Xylabel;
3037         if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3038             im->ximg += Xspacing;
3039         }
3040
3041         if( (im->legendposition == WEST || im->legendposition == EAST) && !(im->extra_flags & NOLEGEND) ){
3042             im->ximg += im->legendwidth;// + Xspacing;
3043         }
3044         if (im->second_axis_scale != 0){
3045             im->ximg += Xylabel;
3046         }
3047
3048         im->ximg += Xvertical + Xvertical2;
3049
3050         if (!(im->extra_flags & NOLEGEND)) {
3051             if(im->legendposition == NORTH || im->legendposition == SOUTH){
3052                 im->legendwidth = im->ximg;
3053                 if (leg_place(im, 0) == -1){
3054                     return -1;
3055                 }
3056             }
3057         }
3058
3059         im->yimg = Ymain + Yxlabel;
3060         if( (im->legendposition == NORTH || im->legendposition == SOUTH)  && !(im->extra_flags & NOLEGEND) ){
3061              im->yimg += im->legendheight;
3062         }
3063
3064         /* reserve space for the title *or* some padding above the graph */
3065         if (Ytitle) {
3066             im->yimg += Ytitle;
3067         } else {
3068             im->yimg += 1.5 * Yspacing;
3069         }
3070         /* reserve space for padding below the graph */
3071         if (im->extra_flags & NOLEGEND) {
3072             im->yimg += Yspacing;
3073         }
3074
3075         if (im->watermark[0] != '\0') {
3076             im->yimg += Ywatermark;
3077         }
3078     }
3079
3080
3081     /* In case of putting the legend in west or east position the first
3082      ** legend calculation might lead to wrong positions if some items
3083      ** are not aligned on the left hand side (e.g. centered) as the
3084      ** legendwidth wight have been increased after the item was placed.
3085      ** In this case the positions have to be recalculated.
3086      */
3087     if (!(im->extra_flags & NOLEGEND)) {
3088         if(im->legendposition == WEST || im->legendposition == EAST){
3089             if (leg_place(im, 0) == -1){
3090                 return -1;
3091             }
3092         }
3093     }
3094
3095     /* After calculating all dimensions
3096      ** it is now possible to calculate
3097      ** all offsets.
3098      */
3099     switch(im->legendposition){
3100         case NORTH:
3101             im->xOriginTitle   = Xvertical + Xylabel + (im->xsize / 2);
3102             im->yOriginTitle   = 0;
3103
3104             im->xOriginLegend  = 0;
3105             im->yOriginLegend  = Ytitle;
3106
3107             im->xOriginLegendY = 0;
3108             im->yOriginLegendY = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3109
3110             im->xorigin        = Xvertical + Xylabel;
3111             im->yorigin        = Ytitle + im->legendheight + Ymain;
3112
3113             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3114             if (im->second_axis_scale != 0){
3115                 im->xOriginLegendY2 += Xylabel;
3116             }
3117             im->yOriginLegendY2 = Ytitle + im->legendheight + (Ymain / 2) + Yxlabel;
3118
3119             break;
3120
3121         case WEST:
3122             im->xOriginTitle   = im->legendwidth + Xvertical + Xylabel + im->xsize / 2;
3123             im->yOriginTitle   = 0;
3124
3125             im->xOriginLegend  = 0;
3126             im->yOriginLegend  = Ytitle;
3127
3128             im->xOriginLegendY = im->legendwidth;
3129             im->yOriginLegendY = Ytitle + (Ymain / 2);
3130
3131             im->xorigin        = im->legendwidth + Xvertical + Xylabel;
3132             im->yorigin        = Ytitle + Ymain;
3133
3134             im->xOriginLegendY2 = im->legendwidth + Xvertical + Xylabel + Xmain;
3135             if (im->second_axis_scale != 0){
3136                 im->xOriginLegendY2 += Xylabel;
3137             }
3138             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3139
3140             break;
3141
3142         case SOUTH:
3143             im->xOriginTitle   = Xvertical + Xylabel + im->xsize / 2;
3144             im->yOriginTitle   = 0;
3145
3146             im->xOriginLegend  = 0;
3147             im->yOriginLegend  = Ytitle + Ymain + Yxlabel;
3148
3149             im->xOriginLegendY = 0;
3150             im->yOriginLegendY = Ytitle + (Ymain / 2);
3151
3152             im->xorigin        = Xvertical + Xylabel;
3153             im->yorigin        = Ytitle + Ymain;
3154
3155             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3156             if (im->second_axis_scale != 0){
3157                 im->xOriginLegendY2 += Xylabel;
3158             }
3159             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3160
3161             break;
3162
3163         case EAST:
3164             im->xOriginTitle   = Xvertical + Xylabel + im->xsize / 2;
3165             im->yOriginTitle   = 0;
3166
3167             im->xOriginLegend  = Xvertical + Xylabel + Xmain + Xvertical2;
3168             if (im->second_axis_scale != 0){
3169                 im->xOriginLegend += Xylabel;
3170             }
3171             im->yOriginLegend  = Ytitle;
3172
3173             im->xOriginLegendY = 0;
3174             im->yOriginLegendY = Ytitle + (Ymain / 2);
3175
3176             im->xorigin        = Xvertical + Xylabel;
3177             im->yorigin        = Ytitle + Ymain;
3178
3179             im->xOriginLegendY2 = Xvertical + Xylabel + Xmain;
3180             if (im->second_axis_scale != 0){
3181                 im->xOriginLegendY2 += Xylabel;
3182             }
3183             im->yOriginLegendY2 = Ytitle + (Ymain / 2);
3184
3185             if (!(im->extra_flags & NO_RRDTOOL_TAG)){
3186                 im->xOriginTitle    += Xspacing;
3187                 im->xOriginLegend   += Xspacing;
3188                 im->xOriginLegendY  += Xspacing;
3189                 im->xorigin         += Xspacing;
3190                 im->xOriginLegendY2 += Xspacing;
3191             }
3192             break;
3193     }
3194
3195     xtr(im, 0);
3196     ytr(im, DNAN);
3197     return 0;
3198 }
3199
3200 static cairo_status_t cairo_output(
3201     void *closure,
3202     const unsigned char
3203     *data,
3204     unsigned int length)
3205 {
3206     image_desc_t *im = (image_desc_t*)closure;
3207
3208     im->rendered_image =
3209         (unsigned char*)realloc(im->rendered_image, im->rendered_image_size + length);
3210     if (im->rendered_image == NULL)
3211         return CAIRO_STATUS_WRITE_ERROR;
3212     memcpy(im->rendered_image + im->rendered_image_size, data, length);
3213     im->rendered_image_size += length;
3214     return CAIRO_STATUS_SUCCESS;
3215 }
3216
3217 /* draw that picture thing ... */
3218 int graph_paint(
3219     image_desc_t *im)
3220 {
3221     int       i, ii;
3222     int       lazy = lazy_check(im);
3223     double    areazero = 0.0;
3224     graph_desc_t *lastgdes = NULL;
3225     rrd_infoval_t info;
3226
3227 //    PangoFontMap *font_map = pango_cairo_font_map_get_default();
3228
3229     /* pull the data from the rrd files ... */
3230     if (data_fetch(im) == -1)
3231         return -1;
3232     /* evaluate VDEF and CDEF operations ... */
3233     if (data_calc(im) == -1)
3234         return -1;
3235     /* calculate and PRINT and GPRINT definitions. We have to do it at
3236      * this point because it will affect the length of the legends
3237      * if there are no graph elements (i==0) we stop here ...
3238      * if we are lazy, try to quit ...
3239      */
3240     i = print_calc(im);
3241     if (i < 0)
3242         return -1;
3243
3244     /* if we want and can be lazy ... quit now */
3245     if (i == 0)
3246         return 0;
3247
3248 /**************************************************************
3249  *** Calculating sizes and locations became a bit confusing ***
3250  *** so I moved this into a separate function.              ***
3251  **************************************************************/
3252     if (graph_size_location(im, i) == -1)
3253         return -1;
3254
3255     info.u_cnt = im->xorigin;
3256     grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
3257     info.u_cnt = im->yorigin - im->ysize;
3258     grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
3259     info.u_cnt = im->xsize;
3260     grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
3261     info.u_cnt = im->ysize;
3262     grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
3263     info.u_cnt = im->ximg;
3264     grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
3265     info.u_cnt = im->yimg;
3266     grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3267     info.u_cnt = im->start;
3268     grinfo_push(im, sprintf_alloc("graph_start"), RD_I_CNT, info);
3269     info.u_cnt = im->end;
3270     grinfo_push(im, sprintf_alloc("graph_end"), RD_I_CNT, info);
3271
3272     /* if we want and can be lazy ... quit now */
3273     if (lazy)
3274         return 0;
3275
3276     /* get actual drawing data and find min and max values */
3277     if (data_proc(im) == -1)
3278         return -1;
3279     if (!im->logarithmic) {
3280         si_unit(im);
3281     }
3282
3283     /* identify si magnitude Kilo, Mega Giga ? */
3284     if (!im->rigid && !im->logarithmic)
3285         expand_range(im);   /* make sure the upper and lower limit are
3286                                sensible values */
3287
3288     info.u_val = im->minval;
3289     grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3290     info.u_val = im->maxval;
3291     grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3292
3293
3294     if (!calc_horizontal_grid(im))
3295         return -1;
3296     /* reset precalc */
3297     ytr(im, DNAN);
3298 /*   if (im->gridfit)
3299      apply_gridfit(im); */
3300     /* the actual graph is created by going through the individual
3301        graph elements and then drawing them */
3302     cairo_surface_destroy(im->surface);
3303     switch (im->imgformat) {
3304     case IF_PNG:
3305         im->surface =
3306             cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3307                                        im->ximg * im->zoom,
3308                                        im->yimg * im->zoom);
3309         break;
3310     case IF_PDF:
3311         im->gridfit = 0;
3312         im->surface = strlen(im->graphfile)
3313             ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3314                                        im->yimg * im->zoom)
3315             : cairo_pdf_surface_create_for_stream
3316             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3317         break;
3318     case IF_EPS:
3319         im->gridfit = 0;
3320         im->surface = strlen(im->graphfile)
3321             ?
3322             cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3323                                     im->yimg * im->zoom)
3324             : cairo_ps_surface_create_for_stream
3325             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3326         break;
3327     case IF_SVG:
3328         im->gridfit = 0;
3329         im->surface = strlen(im->graphfile)
3330             ?
3331             cairo_svg_surface_create(im->
3332                                      graphfile,
3333                                      im->ximg * im->zoom, im->yimg * im->zoom)
3334             : cairo_svg_surface_create_for_stream
3335             (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3336         cairo_svg_surface_restrict_to_version
3337             (im->surface, CAIRO_SVG_VERSION_1_1);
3338         break;
3339     };
3340     cairo_destroy(im->cr);
3341     im->cr = cairo_create(im->surface);
3342     cairo_set_antialias(im->cr, im->graph_antialias);
3343     cairo_scale(im->cr, im->zoom, im->zoom);
3344 //    pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3345     gfx_new_area(im, 0, 0, 0, im->yimg,
3346                  im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3347     gfx_add_point(im, im->ximg, 0);
3348     gfx_close_path(im);
3349     gfx_new_area(im, im->xorigin,
3350                  im->yorigin,
3351                  im->xorigin +
3352                  im->xsize, im->yorigin,
3353                  im->xorigin +
3354                  im->xsize,
3355                  im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3356     gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3357     gfx_close_path(im);
3358     cairo_rectangle(im->cr, im->xorigin, im->yorigin - im->ysize - 1.0,
3359                     im->xsize, im->ysize + 2.0);
3360     cairo_clip(im->cr);
3361     if (im->minval > 0.0)
3362         areazero = im->minval;
3363     if (im->maxval < 0.0)
3364         areazero = im->maxval;
3365     for (i = 0; i < im->gdes_c; i++) {
3366         switch (im->gdes[i].gf) {
3367         case GF_CDEF:
3368         case GF_VDEF:
3369         case GF_DEF:
3370         case GF_PRINT:
3371         case GF_GPRINT:
3372         case GF_COMMENT:
3373         case GF_TEXTALIGN:
3374         case GF_HRULE:
3375         case GF_VRULE:
3376         case GF_XPORT:
3377         case GF_SHIFT:
3378             break;
3379         case GF_TICK:
3380             for (ii = 0; ii < im->xsize; ii++) {
3381                 if (!isnan(im->gdes[i].p_data[ii])
3382                     && im->gdes[i].p_data[ii] != 0.0) {
3383                     if (im->gdes[i].yrule > 0) {
3384                         gfx_line(im,
3385                                  im->xorigin + ii,
3386                                  im->yorigin + 1.0,
3387                                  im->xorigin + ii,
3388                                  im->yorigin -
3389                                  im->gdes[i].yrule *
3390                                  im->ysize, 1.0, im->gdes[i].col);
3391                     } else if (im->gdes[i].yrule < 0) {
3392                         gfx_line(im,
3393                                  im->xorigin + ii,
3394                                  im->yorigin - im->ysize - 1.0,
3395                                  im->xorigin + ii,
3396                                  im->yorigin - im->ysize -
3397                                                 im->gdes[i].
3398                                                 yrule *
3399                                  im->ysize, 1.0, im->gdes[i].col);
3400                     }
3401                 }
3402             }
3403             break;
3404         case GF_LINE:
3405         case GF_AREA:
3406             /* fix data points at oo and -oo */
3407             for (ii = 0; ii < im->xsize; ii++) {
3408                 if (isinf(im->gdes[i].p_data[ii])) {
3409                     if (im->gdes[i].p_data[ii] > 0) {
3410                         im->gdes[i].p_data[ii] = im->maxval;
3411                     } else {
3412                         im->gdes[i].p_data[ii] = im->minval;
3413                     }
3414
3415                 }
3416             }           /* for */
3417
3418             /* *******************************************************
3419                a           ___. (a,t)
3420                |   |    ___
3421                ____|   |   |   |
3422                |       |___|
3423                -------|--t-1--t--------------------------------
3424
3425                if we know the value at time t was a then
3426                we draw a square from t-1 to t with the value a.
3427
3428                ********************************************************* */
3429             if (im->gdes[i].col.alpha != 0.0) {
3430                 /* GF_LINE and friend */
3431                 if (im->gdes[i].gf == GF_LINE) {
3432                     double    last_y = 0.0;
3433                     int       draw_on = 0;
3434
3435                     cairo_save(im->cr);
3436                     cairo_new_path(im->cr);
3437                     cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3438                     if (im->gdes[i].dash) {
3439                         cairo_set_dash(im->cr,
3440                                        im->gdes[i].p_dashes,
3441                                        im->gdes[i].ndash, im->gdes[i].offset);
3442                     }
3443
3444                     for (ii = 1; ii < im->xsize; ii++) {
3445                         if (isnan(im->gdes[i].p_data[ii])
3446                             || (im->slopemode == 1
3447                                 && isnan(im->gdes[i].p_data[ii - 1]))) {
3448                             draw_on = 0;
3449                             continue;
3450                         }
3451                         if (draw_on == 0) {
3452                             last_y = ytr(im, im->gdes[i].p_data[ii]);
3453                             if (im->slopemode == 0) {
3454                                 double    x = ii - 1 + im->xorigin;
3455                                 double    y = last_y;
3456
3457                                 gfx_line_fit(im, &x, &y);
3458                                 cairo_move_to(im->cr, x, y);
3459                                 x = ii + im->xorigin;
3460                                 y = last_y;
3461                                 gfx_line_fit(im, &x, &y);
3462                                 cairo_line_to(im->cr, x, y);
3463                             } else {
3464                                 double    x = ii - 1 + im->xorigin;
3465                                 double    y =
3466                                     ytr(im, im->gdes[i].p_data[ii - 1]);
3467                                 gfx_line_fit(im, &x, &y);
3468                                 cairo_move_to(im->cr, x, y);
3469                                 x = ii + im->xorigin;
3470                                 y = last_y;
3471                                 gfx_line_fit(im, &x, &y);
3472                                 cairo_line_to(im->cr, x, y);
3473                             }
3474                             draw_on = 1;
3475                         } else {
3476                             double    x1 = ii + im->xorigin;
3477                             double    y1 = ytr(im, im->gdes[i].p_data[ii]);
3478
3479                             if (im->slopemode == 0
3480                                 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3481                                 double    x = ii - 1 + im->xorigin;
3482                                 double    y = y1;
3483
3484                                 gfx_line_fit(im, &x, &y);
3485                                 cairo_line_to(im->cr, x, y);
3486                             };
3487                             last_y = y1;
3488                             gfx_line_fit(im, &x1, &y1);
3489                             cairo_line_to(im->cr, x1, y1);
3490                         };
3491                     }
3492                     cairo_set_source_rgba(im->cr,
3493                                           im->gdes[i].
3494                                           col.red,
3495                                           im->gdes[i].
3496                                           col.green,
3497                                           im->gdes[i].
3498                                           col.blue, im->gdes[i].col.alpha);
3499                     cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3500                     cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3501                     cairo_stroke(im->cr);
3502                     cairo_restore(im->cr);
3503                 } else {
3504                     int       idxI = -1;
3505                     double   *foreY =
3506                         (double *) malloc(sizeof(double) * im->xsize * 2);
3507                     double   *foreX =
3508                         (double *) malloc(sizeof(double) * im->xsize * 2);
3509                     double   *backY =
3510                         (double *) malloc(sizeof(double) * im->xsize * 2);
3511                     double   *backX =
3512                         (double *) malloc(sizeof(double) * im->xsize * 2);
3513                     int       drawem = 0;
3514
3515                     for (ii = 0; ii <= im->xsize; ii++) {
3516                         double    ybase, ytop;
3517
3518                         if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3519                             int       cntI = 1;
3520                             int       lastI = 0;
3521
3522                             while (cntI < idxI
3523                                    &&
3524                                    AlmostEqual2sComplement(foreY
3525                                                            [lastI],
3526                                                            foreY[cntI], 4)
3527                                    &&
3528                                    AlmostEqual2sComplement(foreY
3529                                                            [lastI],
3530                                                            foreY
3531                                                            [cntI + 1], 4)) {
3532                                 cntI++;
3533                             }
3534                             gfx_new_area(im,
3535                                          backX[0], backY[0],
3536                                          foreX[0], foreY[0],
3537                                          foreX[cntI],
3538                                          foreY[cntI], im->gdes[i].col);
3539                             while (cntI < idxI) {
3540                                 lastI = cntI;
3541                                 cntI++;
3542                                 while (cntI < idxI
3543                                        &&
3544                                        AlmostEqual2sComplement(foreY
3545                                                                [lastI],
3546                                                                foreY[cntI], 4)
3547                                        &&
3548                                        AlmostEqual2sComplement(foreY
3549                                                                [lastI],
3550                                                                foreY
3551                                                                [cntI
3552                                                                 + 1], 4)) {
3553                                     cntI++;
3554                                 }
3555                                 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3556                             }
3557                             gfx_add_point(im, backX[idxI], backY[idxI]);
3558                             while (idxI > 1) {
3559                                 lastI = idxI;
3560                                 idxI--;
3561                                 while (idxI > 1
3562                                        &&
3563                                        AlmostEqual2sComplement(backY
3564                                                                [lastI],
3565                                                                backY[idxI], 4)
3566                                        &&
3567                                        AlmostEqual2sComplement(backY
3568                                                                [lastI],
3569                                                                backY
3570                                                                [idxI
3571                                                                 - 1], 4)) {
3572                                     idxI--;
3573                                 }
3574                                 gfx_add_point(im, backX[idxI], backY[idxI]);
3575                             }
3576                             idxI = -1;
3577                             drawem = 0;
3578                             gfx_close_path(im);
3579                         }
3580                         if (drawem != 0) {
3581                             drawem = 0;
3582                             idxI = -1;
3583                         }
3584                         if (ii == im->xsize)
3585                             break;
3586                         if (im->slopemode == 0 && ii == 0) {
3587                             continue;
3588                         }
3589                         if (isnan(im->gdes[i].p_data[ii])) {
3590                             drawem = 1;
3591                             continue;
3592                         }
3593                         ytop = ytr(im, im->gdes[i].p_data[ii]);
3594                         if (lastgdes && im->gdes[i].stack) {
3595                             ybase = ytr(im, lastgdes->p_data[ii]);
3596                         } else {
3597                             ybase = ytr(im, areazero);
3598                         }
3599                         if (ybase == ytop) {
3600                             drawem = 1;
3601                             continue;
3602                         }
3603
3604                         if (ybase > ytop) {
3605                             double    extra = ytop;
3606
3607                             ytop = ybase;
3608                             ybase = extra;
3609                         }
3610                         if (im->slopemode == 0) {
3611                             backY[++idxI] = ybase - 0.2;
3612                             backX[idxI] = ii + im->xorigin - 1;
3613                             foreY[idxI] = ytop + 0.2;
3614                             foreX[idxI] = ii + im->xorigin - 1;
3615                         }
3616                         backY[++idxI] = ybase - 0.2;
3617                         backX[idxI] = ii + im->xorigin;
3618                         foreY[idxI] = ytop + 0.2;
3619                         foreX[idxI] = ii + im->xorigin;
3620                     }
3621                     /* close up any remaining area */
3622                     free(foreY);
3623                     free(foreX);
3624                     free(backY);
3625                     free(backX);
3626                 }       /* else GF_LINE */
3627             }
3628             /* if color != 0x0 */
3629             /* make sure we do not run into trouble when stacking on NaN */
3630             for (ii = 0; ii < im->xsize; ii++) {
3631                 if (isnan(im->gdes[i].p_data[ii])) {
3632                     if (lastgdes && (im->gdes[i].stack)) {
3633                         im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3634                     } else {
3635                         im->gdes[i].p_data[ii] = areazero;
3636                     }
3637                 }
3638             }
3639             lastgdes = &(im->gdes[i]);
3640             break;
3641         case GF_STACK:
3642             rrd_set_error
3643                 ("STACK should already be turned into LINE or AREA here");
3644             return -1;
3645             break;
3646         }               /* switch */
3647     }
3648     cairo_reset_clip(im->cr);
3649
3650     /* grid_paint also does the text */
3651     if (!(im->extra_flags & ONLY_GRAPH))
3652         grid_paint(im);
3653     if (!(im->extra_flags & ONLY_GRAPH))
3654         axis_paint(im);
3655     /* the RULES are the last thing to paint ... */
3656     for (i = 0; i < im->gdes_c; i++) {
3657
3658         switch (im->gdes[i].gf) {
3659         case GF_HRULE:
3660             if (im->gdes[i].yrule >= im->minval
3661                 && im->gdes[i].yrule <= im->maxval) {
3662                 cairo_save(im->cr);
3663                 if (im->gdes[i].dash) {
3664                     cairo_set_dash(im->cr,
3665                                    im->gdes[i].p_dashes,
3666                                    im->gdes[i].ndash, im->gdes[i].offset);
3667                 }
3668                 gfx_line(im, im->xorigin,
3669                          ytr(im, im->gdes[i].yrule),
3670                          im->xorigin + im->xsize,
3671                          ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3672                 cairo_stroke(im->cr);
3673                 cairo_restore(im->cr);
3674             }
3675             break;
3676         case GF_VRULE:
3677             if (im->gdes[i].xrule >= im->start
3678                 && im->gdes[i].xrule <= im->end) {
3679                 cairo_save(im->cr);
3680                 if (im->gdes[i].dash) {
3681                     cairo_set_dash(im->cr,
3682                                    im->gdes[i].p_dashes,
3683                                    im->gdes[i].ndash, im->gdes[i].offset);
3684                 }
3685                 gfx_line(im,
3686                          xtr(im, im->gdes[i].xrule),
3687                          im->yorigin, xtr(im,
3688                                           im->
3689                                           gdes[i].
3690                                           xrule),
3691                          im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3692                 cairo_stroke(im->cr);
3693                 cairo_restore(im->cr);
3694             }
3695             break;
3696         default:
3697             break;
3698         }
3699     }
3700
3701
3702     switch (im->imgformat) {
3703     case IF_PNG:
3704     {
3705         cairo_status_t status;
3706
3707         status = strlen(im->graphfile) ?
3708             cairo_surface_write_to_png(im->surface, im->graphfile)
3709             : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3710                                                 im);
3711
3712         if (status != CAIRO_STATUS_SUCCESS) {
3713             rrd_set_error("Could not save png to '%s'", im->graphfile);
3714             return 1;
3715         }
3716         break;
3717     }
3718     default:
3719         if (strlen(im->graphfile)) {
3720             cairo_show_page(im->cr);
3721         } else {
3722             cairo_surface_finish(im->surface);
3723         }
3724         break;
3725     }
3726
3727     return 0;
3728 }
3729
3730
3731 /*****************************************************
3732  * graph stuff
3733  *****************************************************/
3734
3735 int gdes_alloc(
3736     image_desc_t *im)
3737 {
3738
3739     im->gdes_c++;
3740     if ((im->gdes = (graph_desc_t *)
3741          rrd_realloc(im->gdes, (im->gdes_c)
3742                      * sizeof(graph_desc_t))) == NULL) {
3743         rrd_set_error("realloc graph_descs");
3744         return -1;
3745     }
3746
3747
3748     im->gdes[im->gdes_c - 1].step = im->step;
3749     im->gdes[im->gdes_c - 1].step_orig = im->step;
3750     im->gdes[im->gdes_c - 1].stack = 0;
3751     im->gdes[im->gdes_c - 1].linewidth = 0;
3752     im->gdes[im->gdes_c - 1].debug = 0;
3753     im->gdes[im->gdes_c - 1].start = im->start;
3754     im->gdes[im->gdes_c - 1].start_orig = im->start;
3755     im->gdes[im->gdes_c - 1].end = im->end;
3756     im->gdes[im->gdes_c - 1].end_orig = im->end;
3757     im->gdes[im->gdes_c - 1].vname[0] = '\0';
3758     im->gdes[im->gdes_c - 1].data = NULL;
3759     im->gdes[im->gdes_c - 1].ds_namv = NULL;
3760     im->gdes[im->gdes_c - 1].data_first = 0;
3761     im->gdes[im->gdes_c - 1].p_data = NULL;
3762     im->gdes[im->gdes_c - 1].rpnp = NULL;
3763     im->gdes[im->gdes_c - 1].p_dashes = NULL;
3764     im->gdes[im->gdes_c - 1].shift = 0.0;
3765     im->gdes[im->gdes_c - 1].dash = 0;
3766     im->gdes[im->gdes_c - 1].ndash = 0;
3767     im->gdes[im->gdes_c - 1].offset = 0;
3768     im->gdes[im->gdes_c - 1].col.red = 0.0;
3769     im->gdes[im->gdes_c - 1].col.green = 0.0;
3770     im->gdes[im->gdes_c - 1].col.blue = 0.0;
3771     im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3772     im->gdes[im->gdes_c - 1].legend[0] = '\0';
3773     im->gdes[im->gdes_c - 1].format[0] = '\0';
3774     im->gdes[im->gdes_c - 1].strftm = 0;
3775     im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3776     im->gdes[im->gdes_c - 1].ds = -1;
3777     im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3778     im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3779     im->gdes[im->gdes_c - 1].yrule = DNAN;
3780     im->gdes[im->gdes_c - 1].xrule = 0;
3781     return 0;
3782 }
3783
3784 /* copies input untill the first unescaped colon is found
3785    or until input ends. backslashes have to be escaped as well */
3786 int scan_for_col(
3787     const char *const input,
3788     int len,
3789     char *const output)
3790 {
3791     int       inp, outp = 0;
3792
3793     for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3794         if (input[inp] == '\\'
3795             && input[inp + 1] != '\0'
3796             && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3797             output[outp++] = input[++inp];
3798         } else {
3799             output[outp++] = input[inp];
3800         }
3801     }
3802     output[outp] = '\0';
3803     return inp;
3804 }
3805
3806 /* Now just a wrapper around rrd_graph_v */
3807 int rrd_graph(
3808     int argc,
3809     char **argv,
3810     char ***prdata,
3811     int *xsize,
3812     int *ysize,
3813     FILE * stream,
3814     double *ymin,
3815     double *ymax)
3816 {
3817     int       prlines = 0;
3818     rrd_info_t *grinfo = NULL;
3819     rrd_info_t *walker;
3820
3821     grinfo = rrd_graph_v(argc, argv);
3822     if (grinfo == NULL)
3823         return -1;
3824     walker = grinfo;
3825     (*prdata) = NULL;
3826     while (walker) {
3827         if (strcmp(walker->key, "image_info") == 0) {
3828             prlines++;
3829             if (((*prdata) =
3830                  (char**)rrd_realloc((*prdata),
3831                              (prlines + 1) * sizeof(char *))) == NULL) {
3832                 rrd_set_error("realloc prdata");
3833                 return 0;
3834             }
3835             /* imginfo goes to position 0 in the prdata array */
3836             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3837                                              + 2) * sizeof(char));
3838             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3839             (*prdata)[prlines] = NULL;
3840         }
3841         /* skip anything else */
3842         walker = walker->next;
3843     }
3844     walker = grinfo;
3845     *xsize = 0;
3846     *ysize = 0;
3847     *ymin = 0;
3848     *ymax = 0;
3849     while (walker) {
3850         if (strcmp(walker->key, "image_width") == 0) {
3851             *xsize = walker->value.u_cnt;
3852         } else if (strcmp(walker->key, "image_height") == 0) {
3853             *ysize = walker->value.u_cnt;
3854         } else if (strcmp(walker->key, "value_min") == 0) {
3855             *ymin = walker->value.u_val;
3856         } else if (strcmp(walker->key, "value_max") == 0) {
3857             *ymax = walker->value.u_val;
3858         } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3859             prlines++;
3860             if (((*prdata) =
3861                  (char**)rrd_realloc((*prdata),
3862                              (prlines + 1) * sizeof(char *))) == NULL) {
3863                 rrd_set_error("realloc prdata");
3864                 return 0;
3865             }
3866             (*prdata)[prlines - 1] = (char*)malloc((strlen(walker->value.u_str)
3867                                              + 2) * sizeof(char));
3868             (*prdata)[prlines] = NULL;
3869             strcpy((*prdata)[prlines - 1], walker->value.u_str);
3870         } else if (strcmp(walker->key, "image") == 0) {
3871             if ( fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3872                    (stream ? stream : stdout)) == 0 && ferror(stream ? stream : stdout)){
3873                 rrd_set_error("writing image");
3874                 return 0;
3875             }
3876         }
3877         /* skip anything else */
3878         walker = walker->next;
3879     }
3880     rrd_info_free(grinfo);
3881     return 0;
3882 }
3883
3884
3885 /* Some surgery done on this function, it became ridiculously big.
3886 ** Things moved:
3887 ** - initializing     now in rrd_graph_init()
3888 ** - options parsing  now in rrd_graph_options()
3889 ** - script parsing   now in rrd_graph_script()
3890 */
3891 rrd_info_t *rrd_graph_v(
3892     int argc,
3893     char **argv)
3894 {
3895     image_desc_t im;
3896     rrd_info_t *grinfo;