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