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