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