1 /****************************************************************************
2 * RRDtool 1.2.23 Copyright by Tobi Oetiker, 1997-2007
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
8 #include <sys/stat.h>
10 #ifdef WIN32
11 #include "strftime.h"
12 #endif
13 #include "rrd_tool.h"
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
16 #include <io.h>
17 #include <fcntl.h>
18 #endif
20 #ifdef HAVE_TIME_H
21 #include <time.h>
22 #endif
24 #ifdef HAVE_LOCALE_H
25 #include <locale.h>
26 #endif
28 #include "rrd_graph.h"
30 /* some constant definitions */
34 #ifndef RRD_DEFAULT_FONT
35 /* there is special code later to pick Cour.ttf when running on windows */
36 #define RRD_DEFAULT_FONT "DejaVuSansMono-Roman.ttf"
37 #endif
39 text_prop_t text_prop[] = {
40 {8.0, RRD_DEFAULT_FONT}
41 , /* default */
42 {9.0, RRD_DEFAULT_FONT}
43 , /* title */
44 {7.0, RRD_DEFAULT_FONT}
45 , /* axis */
46 {8.0, RRD_DEFAULT_FONT}
47 , /* unit */
48 {8.0, RRD_DEFAULT_FONT} /* legend */
49 };
51 xlab_t xlab[] = {
52 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
53 ,
54 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
55 ,
56 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
57 ,
58 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
59 ,
60 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
61 ,
62 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
63 ,
64 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 4, 0, "%a %H:%M"}
65 ,
66 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
67 ,
68 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
69 ,
70 /*{300, 0, TMT_HOUR,3, TMT_HOUR,12, TMT_HOUR,12, 12*3600,"%a %p"}, this looks silly */
71 {600, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%a"}
72 ,
73 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
74 ,
75 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
76 ,
77 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
78 ,
79 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
80 ,
81 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
82 "Week %V"}
83 ,
84 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
85 "%b"}
86 ,
87 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
88 "%b"}
89 ,
90 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
91 ,
92 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
93 365 * 24 * 3600, "%y"}
94 ,
95 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
96 };
98 /* sensible y label intervals ...*/
100 ylab_t ylab[] = {
101 {0.1, {1, 2, 5, 10}
102 }
103 ,
104 {0.2, {1, 5, 10, 20}
105 }
106 ,
107 {0.5, {1, 2, 4, 10}
108 }
109 ,
110 {1.0, {1, 2, 5, 10}
111 }
112 ,
113 {2.0, {1, 5, 10, 20}
114 }
115 ,
116 {5.0, {1, 2, 4, 10}
117 }
118 ,
119 {10.0, {1, 2, 5, 10}
120 }
121 ,
122 {20.0, {1, 5, 10, 20}
123 }
124 ,
125 {50.0, {1, 2, 4, 10}
126 }
127 ,
128 {100.0, {1, 2, 5, 10}
129 }
130 ,
131 {200.0, {1, 5, 10, 20}
132 }
133 ,
134 {500.0, {1, 2, 4, 10}
135 }
136 ,
137 {0.0, {0, 0, 0, 0}
138 }
139 };
142 gfx_color_t graph_col[] = /* default colors */
143 { 0xFFFFFFFF, /* canvas */
144 0xF0F0F0FF, /* background */
145 0xD0D0D0FF, /* shade A */
146 0xA0A0A0FF, /* shade B */
147 0x90909080, /* grid */
148 0xE0505080, /* major grid */
149 0x000000FF, /* font */
150 0x802020FF, /* arrow */
151 0x202020FF, /* axis */
152 0x000000FF /* frame */
153 };
156 /* #define DEBUG */
158 #ifdef DEBUG
159 # define DPRINT(x) (void)(printf x, printf("\n"))
160 #else
161 # define DPRINT(x)
162 #endif
165 /* initialize with xtr(im,0); */
166 int xtr(
167 image_desc_t * im,
168 time_t mytime)
169 {
170 static double pixie;
172 if (mytime == 0) {
173 pixie = (double) im->xsize / (double) (im->end - im->start);
174 return im->xorigin;
175 }
176 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
177 }
179 /* translate data values into y coordinates */
180 double ytr(
181 image_desc_t * im,
182 double value)
183 {
184 static double pixie;
185 double yval;
187 if (isnan(value)) {
188 if (!im->logarithmic)
189 pixie = (double) im->ysize / (im->maxval - im->minval);
190 else
191 pixie =
192 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
193 yval = im->yorigin;
194 } else if (!im->logarithmic) {
195 yval = im->yorigin - pixie * (value - im->minval);
196 } else {
197 if (value < im->minval) {
198 yval = im->yorigin;
199 } else {
200 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
201 }
202 }
203 /* make sure we don't return anything too unreasonable. GD lib can
204 get terribly slow when drawing lines outside its scope. This is
205 especially problematic in connection with the rigid option */
206 if (!im->rigid) {
207 /* keep yval as-is */
208 } else if (yval > im->yorigin) {
209 yval = im->yorigin + 0.00001;
210 } else if (yval < im->yorigin - im->ysize) {
211 yval = im->yorigin - im->ysize - 0.00001;
212 }
213 return yval;
214 }
218 /* conversion function for symbolic entry names */
221 #define conv_if(VV,VVV) \
222 if (strcmp(#VV, string) == 0) return VVV ;
224 enum gf_en gf_conv(
225 char *string)
226 {
228 conv_if(PRINT, GF_PRINT)
229 conv_if(GPRINT, GF_GPRINT)
230 conv_if(COMMENT, GF_COMMENT)
231 conv_if(HRULE, GF_HRULE)
232 conv_if(VRULE, GF_VRULE)
233 conv_if(LINE, GF_LINE)
234 conv_if(AREA, GF_AREA)
235 conv_if(STACK, GF_STACK)
236 conv_if(TICK, GF_TICK)
237 conv_if(DEF, GF_DEF)
238 conv_if(CDEF, GF_CDEF)
239 conv_if(VDEF, GF_VDEF)
240 #ifdef WITH_PIECHART
241 conv_if(PART, GF_PART)
242 #endif
243 conv_if(XPORT, GF_XPORT)
244 conv_if(SHIFT, GF_SHIFT)
246 return (-1);
247 }
249 enum gfx_if_en if_conv(
250 char *string)
251 {
253 conv_if(PNG, IF_PNG)
254 conv_if(SVG, IF_SVG)
255 conv_if(EPS, IF_EPS)
256 conv_if(PDF, IF_PDF)
258 return (-1);
259 }
261 enum tmt_en tmt_conv(
262 char *string)
263 {
265 conv_if(SECOND, TMT_SECOND)
266 conv_if(MINUTE, TMT_MINUTE)
267 conv_if(HOUR, TMT_HOUR)
268 conv_if(DAY, TMT_DAY)
269 conv_if(WEEK, TMT_WEEK)
270 conv_if(MONTH, TMT_MONTH)
271 conv_if(YEAR, TMT_YEAR)
272 return (-1);
273 }
275 enum grc_en grc_conv(
276 char *string)
277 {
279 conv_if(BACK, GRC_BACK)
280 conv_if(CANVAS, GRC_CANVAS)
281 conv_if(SHADEA, GRC_SHADEA)
282 conv_if(SHADEB, GRC_SHADEB)
283 conv_if(GRID, GRC_GRID)
284 conv_if(MGRID, GRC_MGRID)
285 conv_if(FONT, GRC_FONT)
286 conv_if(ARROW, GRC_ARROW)
287 conv_if(AXIS, GRC_AXIS)
288 conv_if(FRAME, GRC_FRAME)
290 return -1;
291 }
293 enum text_prop_en text_prop_conv(
294 char *string)
295 {
297 conv_if(DEFAULT, TEXT_PROP_DEFAULT)
298 conv_if(TITLE, TEXT_PROP_TITLE)
299 conv_if(AXIS, TEXT_PROP_AXIS)
300 conv_if(UNIT, TEXT_PROP_UNIT)
301 conv_if(LEGEND, TEXT_PROP_LEGEND)
302 return -1;
303 }
306 #undef conv_if
308 int im_free(
309 image_desc_t * im)
310 {
311 unsigned long i, ii;
313 if (im == NULL)
314 return 0;
315 for (i = 0; i < (unsigned) im->gdes_c; i++) {
316 if (im->gdes[i].data_first) {
317 /* careful here, because a single pointer can occur several times */
318 free(im->gdes[i].data);
319 if (im->gdes[i].ds_namv) {
320 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
321 free(im->gdes[i].ds_namv[ii]);
322 free(im->gdes[i].ds_namv);
323 }
324 }
325 free(im->gdes[i].p_data);
326 free(im->gdes[i].rpnp);
327 }
328 free(im->gdes);
329 gfx_destroy(im->canvas);
330 return 0;
331 }
333 /* find SI magnitude symbol for the given number*/
334 void auto_scale(
335 image_desc_t * im, /* image description */
336 double *value,
337 char **symb_ptr,
338 double *magfact)
339 {
341 char *symbol[] = { "a", /* 10e-18 Atto */
342 "f", /* 10e-15 Femto */
343 "p", /* 10e-12 Pico */
344 "n", /* 10e-9 Nano */
345 "u", /* 10e-6 Micro */
346 "m", /* 10e-3 Milli */
347 " ", /* Base */
348 "k", /* 10e3 Kilo */
349 "M", /* 10e6 Mega */
350 "G", /* 10e9 Giga */
351 "T", /* 10e12 Tera */
352 "P", /* 10e15 Peta */
353 "E"
354 }; /* 10e18 Exa */
356 int symbcenter = 6;
357 int sindex;
359 if (*value == 0.0 || isnan(*value)) {
360 sindex = 0;
361 *magfact = 1.0;
362 } else {
363 sindex = floor(log(fabs(*value)) / log((double) im->base));
364 *magfact = pow((double) im->base, (double) sindex);
365 (*value) /= (*magfact);
366 }
367 if (sindex <= symbcenter && sindex >= -symbcenter) {
368 (*symb_ptr) = symbol[sindex + symbcenter];
369 } else {
370 (*symb_ptr) = "?";
371 }
372 }
375 static char si_symbol[] = {
376 'a', /* 10e-18 Atto */
377 'f', /* 10e-15 Femto */
378 'p', /* 10e-12 Pico */
379 'n', /* 10e-9 Nano */
380 'u', /* 10e-6 Micro */
381 'm', /* 10e-3 Milli */
382 ' ', /* Base */
383 'k', /* 10e3 Kilo */
384 'M', /* 10e6 Mega */
385 'G', /* 10e9 Giga */
386 'T', /* 10e12 Tera */
387 'P', /* 10e15 Peta */
388 'E', /* 10e18 Exa */
389 };
390 static const int si_symbcenter = 6;
392 /* find SI magnitude symbol for the numbers on the y-axis*/
393 void si_unit(
394 image_desc_t * im /* image description */
395 )
396 {
398 double digits, viewdigits = 0;
400 digits =
401 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
402 log((double) im->base));
404 if (im->unitsexponent != 9999) {
405 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
406 viewdigits = floor(im->unitsexponent / 3);
407 } else {
408 viewdigits = digits;
409 }
411 im->magfact = pow((double) im->base, digits);
413 #ifdef DEBUG
414 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
415 #endif
417 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
419 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
420 ((viewdigits + si_symbcenter) >= 0))
421 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
422 else
423 im->symbol = '?';
424 }
426 /* move min and max values around to become sensible */
428 void expand_range(
429 image_desc_t * im)
430 {
431 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
432 600.0, 500.0, 400.0, 300.0, 250.0,
433 200.0, 125.0, 100.0, 90.0, 80.0,
434 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
435 25.0, 20.0, 10.0, 9.0, 8.0,
436 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
437 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
438 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
439 };
441 double scaled_min, scaled_max;
442 double adj;
443 int i;
447 #ifdef DEBUG
448 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
449 im->minval, im->maxval, im->magfact);
450 #endif
452 if (isnan(im->ygridstep)) {
453 if (im->extra_flags & ALTAUTOSCALE) {
454 /* measure the amplitude of the function. Make sure that
455 graph boundaries are slightly higher then max/min vals
456 so we can see amplitude on the graph */
457 double delt, fact;
459 delt = im->maxval - im->minval;
460 adj = delt * 0.1;
461 fact = 2.0 * pow(10.0,
462 floor(log10
463 (max(fabs(im->minval), fabs(im->maxval)) /
464 im->magfact)) - 2);
465 if (delt < fact) {
466 adj = (fact - delt) * 0.55;
467 #ifdef DEBUG
468 printf
469 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
470 im->minval, im->maxval, delt, fact, adj);
471 #endif
472 }
473 im->minval -= adj;
474 im->maxval += adj;
475 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
476 /* measure the amplitude of the function. Make sure that
477 graph boundaries are slightly lower than min vals
478 so we can see amplitude on the graph */
479 adj = (im->maxval - im->minval) * 0.1;
480 im->minval -= adj;
481 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
482 /* measure the amplitude of the function. Make sure that
483 graph boundaries are slightly higher than max vals
484 so we can see amplitude on the graph */
485 adj = (im->maxval - im->minval) * 0.1;
486 im->maxval += adj;
487 } else {
488 scaled_min = im->minval / im->magfact;
489 scaled_max = im->maxval / im->magfact;
491 for (i = 1; sensiblevalues[i] > 0; i++) {
492 if (sensiblevalues[i - 1] >= scaled_min &&
493 sensiblevalues[i] <= scaled_min)
494 im->minval = sensiblevalues[i] * (im->magfact);
496 if (-sensiblevalues[i - 1] <= scaled_min &&
497 -sensiblevalues[i] >= scaled_min)
498 im->minval = -sensiblevalues[i - 1] * (im->magfact);
500 if (sensiblevalues[i - 1] >= scaled_max &&
501 sensiblevalues[i] <= scaled_max)
502 im->maxval = sensiblevalues[i - 1] * (im->magfact);
504 if (-sensiblevalues[i - 1] <= scaled_max &&
505 -sensiblevalues[i] >= scaled_max)
506 im->maxval = -sensiblevalues[i] * (im->magfact);
507 }
508 }
509 } else {
510 /* adjust min and max to the grid definition if there is one */
511 im->minval = (double) im->ylabfact * im->ygridstep *
512 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
513 im->maxval = (double) im->ylabfact * im->ygridstep *
514 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
515 }
517 #ifdef DEBUG
518 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
519 im->minval, im->maxval, im->magfact);
520 #endif
521 }
523 void apply_gridfit(
524 image_desc_t * im)
525 {
526 if (isnan(im->minval) || isnan(im->maxval))
527 return;
528 ytr(im, DNAN);
529 if (im->logarithmic) {
530 double ya, yb, ypix, ypixfrac;
531 double log10_range = log10(im->maxval) - log10(im->minval);
533 ya = pow((double) 10, floor(log10(im->minval)));
534 while (ya < im->minval)
535 ya *= 10;
536 if (ya > im->maxval)
537 return; /* don't have y=10^x gridline */
538 yb = ya * 10;
539 if (yb <= im->maxval) {
540 /* we have at least 2 y=10^x gridlines.
541 Make sure distance between them in pixels
542 are an integer by expanding im->maxval */
543 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
544 double factor = y_pixel_delta / floor(y_pixel_delta);
545 double new_log10_range = factor * log10_range;
546 double new_ymax_log10 = log10(im->minval) + new_log10_range;
548 im->maxval = pow(10, new_ymax_log10);
549 ytr(im, DNAN); /* reset precalc */
550 log10_range = log10(im->maxval) - log10(im->minval);
551 }
552 /* make sure first y=10^x gridline is located on
553 integer pixel position by moving scale slightly
554 downwards (sub-pixel movement) */
555 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
556 ypixfrac = ypix - floor(ypix);
557 if (ypixfrac > 0 && ypixfrac < 1) {
558 double yfrac = ypixfrac / im->ysize;
560 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
561 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
562 ytr(im, DNAN); /* reset precalc */
563 }
564 } else {
565 /* Make sure we have an integer pixel distance between
566 each minor gridline */
567 double ypos1 = ytr(im, im->minval);
568 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
569 double y_pixel_delta = ypos1 - ypos2;
570 double factor = y_pixel_delta / floor(y_pixel_delta);
571 double new_range = factor * (im->maxval - im->minval);
572 double gridstep = im->ygrid_scale.gridstep;
573 double minor_y, minor_y_px, minor_y_px_frac;
575 if (im->maxval > 0.0)
576 im->maxval = im->minval + new_range;
577 else
578 im->minval = im->maxval - new_range;
579 ytr(im, DNAN); /* reset precalc */
580 /* make sure first minor gridline is on integer pixel y coord */
581 minor_y = gridstep * floor(im->minval / gridstep);
582 while (minor_y < im->minval)
583 minor_y += gridstep;
584 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
585 minor_y_px_frac = minor_y_px - floor(minor_y_px);
586 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
587 double yfrac = minor_y_px_frac / im->ysize;
588 double range = im->maxval - im->minval;
590 im->minval = im->minval - yfrac * range;
591 im->maxval = im->maxval - yfrac * range;
592 ytr(im, DNAN); /* reset precalc */
593 }
594 calc_horizontal_grid(im); /* recalc with changed im->maxval */
595 }
596 }
598 /* reduce data reimplementation by Alex */
600 void reduce_data(
601 enum cf_en cf, /* which consolidation function ? */
602 unsigned long cur_step, /* step the data currently is in */
603 time_t *start, /* start, end and step as requested ... */
604 time_t *end, /* ... by the application will be ... */
605 unsigned long *step, /* ... adjusted to represent reality */
606 unsigned long *ds_cnt, /* number of data sources in file */
607 rrd_value_t ** data)
608 { /* two dimensional array containing the data */
609 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
610 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
611 0;
612 rrd_value_t *srcptr, *dstptr;
614 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
615 dstptr = *data;
616 srcptr = *data;
617 row_cnt = ((*end) - (*start)) / cur_step;
619 #ifdef DEBUG
620 #define DEBUG_REDUCE
621 #endif
622 #ifdef DEBUG_REDUCE
623 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
624 row_cnt, reduce_factor, *start, *end, cur_step);
625 for (col = 0; col < row_cnt; col++) {
626 printf("time %10lu: ", *start + (col + 1) * cur_step);
627 for (i = 0; i < *ds_cnt; i++)
628 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
629 printf("\n");
630 }
631 #endif
633 /* We have to combine [reduce_factor] rows of the source
634 ** into one row for the destination. Doing this we also
635 ** need to take care to combine the correct rows. First
636 ** alter the start and end time so that they are multiples
637 ** of the new step time. We cannot reduce the amount of
638 ** time so we have to move the end towards the future and
639 ** the start towards the past.
640 */
641 end_offset = (*end) % (*step);
642 start_offset = (*start) % (*step);
644 /* If there is a start offset (which cannot be more than
645 ** one destination row), skip the appropriate number of
646 ** source rows and one destination row. The appropriate
647 ** number is what we do know (start_offset/cur_step) of
648 ** the new interval (*step/cur_step aka reduce_factor).
649 */
650 #ifdef DEBUG_REDUCE
651 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
652 printf("row_cnt before: %lu\n", row_cnt);
653 #endif
654 if (start_offset) {
655 (*start) = (*start) - start_offset;
656 skiprows = reduce_factor - start_offset / cur_step;
657 srcptr += skiprows * *ds_cnt;
658 for (col = 0; col < (*ds_cnt); col++)
659 *dstptr++ = DNAN;
660 row_cnt -= skiprows;
661 }
662 #ifdef DEBUG_REDUCE
663 printf("row_cnt between: %lu\n", row_cnt);
664 #endif
666 /* At the end we have some rows that are not going to be
667 ** used, the amount is end_offset/cur_step
668 */
669 if (end_offset) {
670 (*end) = (*end) - end_offset + (*step);
671 skiprows = end_offset / cur_step;
672 row_cnt -= skiprows;
673 }
674 #ifdef DEBUG_REDUCE
675 printf("row_cnt after: %lu\n", row_cnt);
676 #endif
678 /* Sanity check: row_cnt should be multiple of reduce_factor */
679 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
681 if (row_cnt % reduce_factor) {
682 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
683 row_cnt, reduce_factor);
684 printf("BUG in reduce_data()\n");
685 exit(1);
686 }
688 /* Now combine reduce_factor intervals at a time
689 ** into one interval for the destination.
690 */
692 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
693 for (col = 0; col < (*ds_cnt); col++) {
694 rrd_value_t newval = DNAN;
695 unsigned long validval = 0;
697 for (i = 0; i < reduce_factor; i++) {
698 if (isnan(srcptr[i * (*ds_cnt) + col])) {
699 continue;
700 }
701 validval++;
702 if (isnan(newval))
703 newval = srcptr[i * (*ds_cnt) + col];
704 else {
705 switch (cf) {
706 case CF_HWPREDICT:
707 case CF_DEVSEASONAL:
708 case CF_DEVPREDICT:
709 case CF_SEASONAL:
710 case CF_AVERAGE:
711 newval += srcptr[i * (*ds_cnt) + col];
712 break;
713 case CF_MINIMUM:
714 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
715 break;
716 case CF_FAILURES:
717 /* an interval contains a failure if any subintervals contained a failure */
718 case CF_MAXIMUM:
719 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
720 break;
721 case CF_LAST:
722 newval = srcptr[i * (*ds_cnt) + col];
723 break;
724 }
725 }
726 }
727 if (validval == 0) {
728 newval = DNAN;
729 } else {
730 switch (cf) {
731 case CF_HWPREDICT:
732 case CF_DEVSEASONAL:
733 case CF_DEVPREDICT:
734 case CF_SEASONAL:
735 case CF_AVERAGE:
736 newval /= validval;
737 break;
738 case CF_MINIMUM:
739 case CF_FAILURES:
740 case CF_MAXIMUM:
741 case CF_LAST:
742 break;
743 }
744 }
745 *dstptr++ = newval;
746 }
747 srcptr += (*ds_cnt) * reduce_factor;
748 row_cnt -= reduce_factor;
749 }
750 /* If we had to alter the endtime, we didn't have enough
751 ** source rows to fill the last row. Fill it with NaN.
752 */
753 if (end_offset)
754 for (col = 0; col < (*ds_cnt); col++)
755 *dstptr++ = DNAN;
756 #ifdef DEBUG_REDUCE
757 row_cnt = ((*end) - (*start)) / *step;
758 srcptr = *data;
759 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
760 row_cnt, *start, *end, *step);
761 for (col = 0; col < row_cnt; col++) {
762 printf("time %10lu: ", *start + (col + 1) * (*step));
763 for (i = 0; i < *ds_cnt; i++)
764 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
765 printf("\n");
766 }
767 #endif
768 }
771 /* get the data required for the graphs from the
772 relevant rrds ... */
774 int data_fetch(
775 image_desc_t * im)
776 {
777 int i, ii;
778 int skip;
780 /* pull the data from the rrd files ... */
781 for (i = 0; i < (int) im->gdes_c; i++) {
782 /* only GF_DEF elements fetch data */
783 if (im->gdes[i].gf != GF_DEF)
784 continue;
786 skip = 0;
787 /* do we have it already ? */
788 for (ii = 0; ii < i; ii++) {
789 if (im->gdes[ii].gf != GF_DEF)
790 continue;
791 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
792 && (im->gdes[i].cf == im->gdes[ii].cf)
793 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
794 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
795 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
796 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
797 /* OK, the data is already there.
798 ** Just copy the header portion
799 */
800 im->gdes[i].start = im->gdes[ii].start;
801 im->gdes[i].end = im->gdes[ii].end;
802 im->gdes[i].step = im->gdes[ii].step;
803 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
804 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
805 im->gdes[i].data = im->gdes[ii].data;
806 im->gdes[i].data_first = 0;
807 skip = 1;
808 }
809 if (skip)
810 break;
811 }
812 if (!skip) {
813 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
815 if ((rrd_fetch_fn(im->gdes[i].rrd,
816 im->gdes[i].cf,
817 &im->gdes[i].start,
818 &im->gdes[i].end,
819 &ft_step,
820 &im->gdes[i].ds_cnt,
821 &im->gdes[i].ds_namv,
822 &im->gdes[i].data)) == -1) {
823 return -1;
824 }
825 im->gdes[i].data_first = 1;
827 if (ft_step < im->gdes[i].step) {
828 reduce_data(im->gdes[i].cf_reduce,
829 ft_step,
830 &im->gdes[i].start,
831 &im->gdes[i].end,
832 &im->gdes[i].step,
833 &im->gdes[i].ds_cnt, &im->gdes[i].data);
834 } else {
835 im->gdes[i].step = ft_step;
836 }
837 }
839 /* lets see if the required data source is really there */
840 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
841 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
842 im->gdes[i].ds = ii;
843 }
844 }
845 if (im->gdes[i].ds == -1) {
846 rrd_set_error("No DS called '%s' in '%s'",
847 im->gdes[i].ds_nam, im->gdes[i].rrd);
848 return -1;
849 }
851 }
852 return 0;
853 }
855 /* evaluate the expressions in the CDEF functions */
857 /*************************************************************
858 * CDEF stuff
859 *************************************************************/
861 long find_var_wrapper(
862 void *arg1,
863 char *key)
864 {
865 return find_var((image_desc_t *) arg1, key);
866 }
868 /* find gdes containing var*/
869 long find_var(
870 image_desc_t * im,
871 char *key)
872 {
873 long ii;
875 for (ii = 0; ii < im->gdes_c - 1; ii++) {
876 if ((im->gdes[ii].gf == GF_DEF
877 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
878 && (strcmp(im->gdes[ii].vname, key) == 0)) {
879 return ii;
880 }
881 }
882 return -1;
883 }
885 /* find the largest common denominator for all the numbers
886 in the 0 terminated num array */
887 long lcd(
888 long *num)
889 {
890 long rest;
891 int i;
893 for (i = 0; num[i + 1] != 0; i++) {
894 do {
895 rest = num[i] % num[i + 1];
896 num[i] = num[i + 1];
897 num[i + 1] = rest;
898 } while (rest != 0);
899 num[i + 1] = num[i];
900 }
901 /* return i==0?num[i]:num[i-1]; */
902 return num[i];
903 }
905 /* run the rpn calculator on all the VDEF and CDEF arguments */
906 int data_calc(
907 image_desc_t * im)
908 {
910 int gdi;
911 int dataidx;
912 long *steparray, rpi;
913 int stepcnt;
914 time_t now;
915 rpnstack_t rpnstack;
917 rpnstack_init(&rpnstack);
919 for (gdi = 0; gdi < im->gdes_c; gdi++) {
920 /* Look for GF_VDEF and GF_CDEF in the same loop,
921 * so CDEFs can use VDEFs and vice versa
922 */
923 switch (im->gdes[gdi].gf) {
924 case GF_XPORT:
925 break;
926 case GF_SHIFT:{
927 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
929 /* remove current shift */
930 vdp->start -= vdp->shift;
931 vdp->end -= vdp->shift;
933 /* vdef */
934 if (im->gdes[gdi].shidx >= 0)
935 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
936 /* constant */
937 else
938 vdp->shift = im->gdes[gdi].shval;
940 /* normalize shift to multiple of consolidated step */
941 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
943 /* apply shift */
944 vdp->start += vdp->shift;
945 vdp->end += vdp->shift;
946 break;
947 }
948 case GF_VDEF:
949 /* A VDEF has no DS. This also signals other parts
950 * of rrdtool that this is a VDEF value, not a CDEF.
951 */
952 im->gdes[gdi].ds_cnt = 0;
953 if (vdef_calc(im, gdi)) {
954 rrd_set_error("Error processing VDEF '%s'",
955 im->gdes[gdi].vname);
956 rpnstack_free(&rpnstack);
957 return -1;
958 }
959 break;
960 case GF_CDEF:
961 im->gdes[gdi].ds_cnt = 1;
962 im->gdes[gdi].ds = 0;
963 im->gdes[gdi].data_first = 1;
964 im->gdes[gdi].start = 0;
965 im->gdes[gdi].end = 0;
966 steparray = NULL;
967 stepcnt = 0;
968 dataidx = -1;
970 /* Find the variables in the expression.
971 * - VDEF variables are substituted by their values
972 * and the opcode is changed into OP_NUMBER.
973 * - CDEF variables are analized for their step size,
974 * the lowest common denominator of all the step
975 * sizes of the data sources involved is calculated
976 * and the resulting number is the step size for the
977 * resulting data source.
978 */
979 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
980 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
981 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
982 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
984 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
985 #if 0
986 printf
987 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
988 im->gdes[gdi].vname, im->gdes[ptr].vname);
989 printf("DEBUG: value from vdef is %f\n",
990 im->gdes[ptr].vf.val);
991 #endif
992 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
993 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
994 } else { /* normal variables and PREF(variables) */
996 /* add one entry to the array that keeps track of the step sizes of the
997 * data sources going into the CDEF. */
998 if ((steparray =
999 rrd_realloc(steparray,
1000 (++stepcnt +
1001 1) * sizeof(*steparray))) == NULL) {
1002 rrd_set_error("realloc steparray");
1003 rpnstack_free(&rpnstack);
1004 return -1;
1005 };
1007 steparray[stepcnt - 1] = im->gdes[ptr].step;
1009 /* adjust start and end of cdef (gdi) so
1010 * that it runs from the latest start point
1011 * to the earliest endpoint of any of the
1012 * rras involved (ptr)
1013 */
1015 if (im->gdes[gdi].start < im->gdes[ptr].start)
1016 im->gdes[gdi].start = im->gdes[ptr].start;
1018 if (im->gdes[gdi].end == 0 ||
1019 im->gdes[gdi].end > im->gdes[ptr].end)
1020 im->gdes[gdi].end = im->gdes[ptr].end;
1022 /* store pointer to the first element of
1023 * the rra providing data for variable,
1024 * further save step size and data source
1025 * count of this rra
1026 */
1027 im->gdes[gdi].rpnp[rpi].data =
1028 im->gdes[ptr].data + im->gdes[ptr].ds;
1029 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1030 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1032 /* backoff the *.data ptr; this is done so
1033 * rpncalc() function doesn't have to treat
1034 * the first case differently
1035 */
1036 } /* if ds_cnt != 0 */
1037 } /* if OP_VARIABLE */
1038 } /* loop through all rpi */
1040 /* move the data pointers to the correct period */
1041 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1042 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1043 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1044 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1045 long diff =
1046 im->gdes[gdi].start - im->gdes[ptr].start;
1048 if (diff > 0)
1049 im->gdes[gdi].rpnp[rpi].data +=
1050 (diff / im->gdes[ptr].step) *
1051 im->gdes[ptr].ds_cnt;
1052 }
1053 }
1055 if (steparray == NULL) {
1056 rrd_set_error("rpn expressions without DEF"
1057 " or CDEF variables are not supported");
1058 rpnstack_free(&rpnstack);
1059 return -1;
1060 }
1061 steparray[stepcnt] = 0;
1062 /* Now find the resulting step. All steps in all
1063 * used RRAs have to be visited
1064 */
1065 im->gdes[gdi].step = lcd(steparray);
1066 free(steparray);
1067 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1068 im->gdes[gdi].start)
1069 / im->gdes[gdi].step)
1070 * sizeof(double))) == NULL) {
1071 rrd_set_error("malloc im->gdes[gdi].data");
1072 rpnstack_free(&rpnstack);
1073 return -1;
1074 }
1076 /* Step through the new cdef results array and
1077 * calculate the values
1078 */
1079 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1080 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1081 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1083 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1084 * in this case we are advancing by timesteps;
1085 * we use the fact that time_t is a synonym for long
1086 */
1087 if (rpn_calc(rpnp, &rpnstack, (long) now,
1088 im->gdes[gdi].data, ++dataidx) == -1) {
1089 /* rpn_calc sets the error string */
1090 rpnstack_free(&rpnstack);
1091 return -1;
1092 }
1093 } /* enumerate over time steps within a CDEF */
1094 break;
1095 default:
1096 continue;
1097 }
1098 } /* enumerate over CDEFs */
1099 rpnstack_free(&rpnstack);
1100 return 0;
1101 }
1103 /* massage data so, that we get one value for each x coordinate in the graph */
1104 int data_proc(
1105 image_desc_t * im)
1106 {
1107 long i, ii;
1108 double pixstep = (double) (im->end - im->start)
1109 / (double) im->xsize; /* how much time
1110 passes in one pixel */
1111 double paintval;
1112 double minval = DNAN, maxval = DNAN;
1114 unsigned long gr_time;
1116 /* memory for the processed data */
1117 for (i = 0; i < im->gdes_c; i++) {
1118 if ((im->gdes[i].gf == GF_LINE) ||
1119 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1120 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1121 * sizeof(rrd_value_t))) == NULL) {
1122 rrd_set_error("malloc data_proc");
1123 return -1;
1124 }
1125 }
1126 }
1128 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1129 long vidx;
1131 gr_time = im->start + pixstep * i; /* time of the current step */
1132 paintval = 0.0;
1134 for (ii = 0; ii < im->gdes_c; ii++) {
1135 double value;
1137 switch (im->gdes[ii].gf) {
1138 case GF_LINE:
1139 case GF_AREA:
1140 case GF_TICK:
1141 if (!im->gdes[ii].stack)
1142 paintval = 0.0;
1143 value = im->gdes[ii].yrule;
1144 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1145 /* The time of the data doesn't necessarily match
1146 ** the time of the graph. Beware.
1147 */
1148 vidx = im->gdes[ii].vidx;
1149 if (im->gdes[vidx].gf == GF_VDEF) {
1150 value = im->gdes[vidx].vf.val;
1151 } else
1152 if (((long int) gr_time >=
1153 (long int) im->gdes[vidx].start)
1154 && ((long int) gr_time <=
1155 (long int) im->gdes[vidx].end)) {
1156 value = im->gdes[vidx].data[(unsigned long)
1157 floor((double)
1158 (gr_time -
1159 im->gdes[vidx].
1160 start)
1161 /
1162 im->gdes[vidx].step)
1163 * im->gdes[vidx].ds_cnt +
1164 im->gdes[vidx].ds];
1165 } else {
1166 value = DNAN;
1167 }
1168 };
1170 if (!isnan(value)) {
1171 paintval += value;
1172 im->gdes[ii].p_data[i] = paintval;
1173 /* GF_TICK: the data values are not
1174 ** relevant for min and max
1175 */
1176 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1177 if ((isnan(minval) || paintval < minval) &&
1178 !(im->logarithmic && paintval <= 0.0))
1179 minval = paintval;
1180 if (isnan(maxval) || paintval > maxval)
1181 maxval = paintval;
1182 }
1183 } else {
1184 im->gdes[ii].p_data[i] = DNAN;
1185 }
1186 break;
1187 case GF_STACK:
1188 rrd_set_error
1189 ("STACK should already be turned into LINE or AREA here");
1190 return -1;
1191 break;
1192 default:
1193 break;
1194 }
1195 }
1196 }
1198 /* if min or max have not been asigned a value this is because
1199 there was no data in the graph ... this is not good ...
1200 lets set these to dummy values then ... */
1202 if (im->logarithmic) {
1203 if (isnan(minval))
1204 minval = 0.2;
1205 if (isnan(maxval))
1206 maxval = 5.1;
1207 } else {
1208 if (isnan(minval))
1209 minval = 0.0;
1210 if (isnan(maxval))
1211 maxval = 1.0;
1212 }
1214 /* adjust min and max values */
1215 if (isnan(im->minval)
1216 /* don't adjust low-end with log scale *//* why not? */
1217 || ((!im->rigid) && im->minval > minval)
1218 ) {
1219 if (im->logarithmic)
1220 im->minval = minval * 0.5;
1221 else
1222 im->minval = minval;
1223 }
1224 if (isnan(im->maxval)
1225 || (!im->rigid && im->maxval < maxval)
1226 ) {
1227 if (im->logarithmic)
1228 im->maxval = maxval * 2.0;
1229 else
1230 im->maxval = maxval;
1231 }
1232 /* make sure min is smaller than max */
1233 if (im->minval > im->maxval) {
1234 im->minval = 0.99 * im->maxval;
1235 }
1237 /* make sure min and max are not equal */
1238 if (im->minval == im->maxval) {
1239 im->maxval *= 1.01;
1240 if (!im->logarithmic) {
1241 im->minval *= 0.99;
1242 }
1243 /* make sure min and max are not both zero */
1244 if (im->maxval == 0.0) {
1245 im->maxval = 1.0;
1246 }
1247 }
1248 return 0;
1249 }
1253 /* identify the point where the first gridline, label ... gets placed */
1255 time_t find_first_time(
1256 time_t start, /* what is the initial time */
1257 enum tmt_en baseint, /* what is the basic interval */
1258 long basestep /* how many if these do we jump a time */
1259 )
1260 {
1261 struct tm tm;
1263 localtime_r(&start, &tm);
1264 switch (baseint) {
1265 case TMT_SECOND:
1266 tm.tm_sec -= tm.tm_sec % basestep;
1267 break;
1268 case TMT_MINUTE:
1269 tm.tm_sec = 0;
1270 tm.tm_min -= tm.tm_min % basestep;
1271 break;
1272 case TMT_HOUR:
1273 tm.tm_sec = 0;
1274 tm.tm_min = 0;
1275 tm.tm_hour -= tm.tm_hour % basestep;
1276 break;
1277 case TMT_DAY:
1278 /* we do NOT look at the basestep for this ... */
1279 tm.tm_sec = 0;
1280 tm.tm_min = 0;
1281 tm.tm_hour = 0;
1282 break;
1283 case TMT_WEEK:
1284 /* we do NOT look at the basestep for this ... */
1285 tm.tm_sec = 0;
1286 tm.tm_min = 0;
1287 tm.tm_hour = 0;
1288 tm.tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1289 if (tm.tm_wday == 0)
1290 tm.tm_mday -= 7; /* we want the *previous* monday */
1291 break;
1292 case TMT_MONTH:
1293 tm.tm_sec = 0;
1294 tm.tm_min = 0;
1295 tm.tm_hour = 0;
1296 tm.tm_mday = 1;
1297 tm.tm_mon -= tm.tm_mon % basestep;
1298 break;
1300 case TMT_YEAR:
1301 tm.tm_sec = 0;
1302 tm.tm_min = 0;
1303 tm.tm_hour = 0;
1304 tm.tm_mday = 1;
1305 tm.tm_mon = 0;
1306 tm.tm_year -= (tm.tm_year + 1900) % basestep;
1308 }
1309 return mktime(&tm);
1310 }
1312 /* identify the point where the next gridline, label ... gets placed */
1313 time_t find_next_time(
1314 time_t current, /* what is the initial time */
1315 enum tmt_en baseint, /* what is the basic interval */
1316 long basestep /* how many if these do we jump a time */
1317 )
1318 {
1319 struct tm tm;
1320 time_t madetime;
1322 localtime_r(¤t, &tm);
1323 do {
1324 switch (baseint) {
1325 case TMT_SECOND:
1326 tm.tm_sec += basestep;
1327 break;
1328 case TMT_MINUTE:
1329 tm.tm_min += basestep;
1330 break;
1331 case TMT_HOUR:
1332 tm.tm_hour += basestep;
1333 break;
1334 case TMT_DAY:
1335 tm.tm_mday += basestep;
1336 break;
1337 case TMT_WEEK:
1338 tm.tm_mday += 7 * basestep;
1339 break;
1340 case TMT_MONTH:
1341 tm.tm_mon += basestep;
1342 break;
1343 case TMT_YEAR:
1344 tm.tm_year += basestep;
1345 }
1346 madetime = mktime(&tm);
1347 } while (madetime == -1); /* this is necessary to skip impssible times
1348 like the daylight saving time skips */
1349 return madetime;
1351 }
1354 /* calculate values required for PRINT and GPRINT functions */
1356 int print_calc(
1357 image_desc_t * im,
1358 char ***prdata)
1359 {
1360 long i, ii, validsteps;
1361 double printval;
1362 struct tm tmvdef;
1363 int graphelement = 0;
1364 long vidx;
1365 int max_ii;
1366 double magfact = -1;
1367 char *si_symb = "";
1368 char *percent_s;
1369 int prlines = 1;
1371 /* wow initializing tmvdef is quite a task :-) */
1372 time_t now = time(NULL);
1374 localtime_r(&now, &tmvdef);
1375 if (im->imginfo)
1376 prlines++;
1377 for (i = 0; i < im->gdes_c; i++) {
1378 vidx = im->gdes[i].vidx;
1379 switch (im->gdes[i].gf) {
1380 case GF_PRINT:
1381 prlines++;
1382 if (((*prdata) =
1383 rrd_realloc((*prdata), prlines * sizeof(char *))) == NULL) {
1384 rrd_set_error("realloc prdata");
1385 return 0;
1386 }
1387 case GF_GPRINT:
1388 /* PRINT and GPRINT can now print VDEF generated values.
1389 * There's no need to do any calculations on them as these
1390 * calculations were already made.
1391 */
1392 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1393 printval = im->gdes[vidx].vf.val;
1394 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1395 } else { /* need to calculate max,min,avg etcetera */
1396 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1397 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1398 printval = DNAN;
1399 validsteps = 0;
1400 for (ii = im->gdes[vidx].ds;
1401 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1402 if (!finite(im->gdes[vidx].data[ii]))
1403 continue;
1404 if (isnan(printval)) {
1405 printval = im->gdes[vidx].data[ii];
1406 validsteps++;
1407 continue;
1408 }
1410 switch (im->gdes[i].cf) {
1411 case CF_HWPREDICT:
1412 case CF_DEVPREDICT:
1413 case CF_DEVSEASONAL:
1414 case CF_SEASONAL:
1415 case CF_AVERAGE:
1416 validsteps++;
1417 printval += im->gdes[vidx].data[ii];
1418 break;
1419 case CF_MINIMUM:
1420 printval = min(printval, im->gdes[vidx].data[ii]);
1421 break;
1422 case CF_FAILURES:
1423 case CF_MAXIMUM:
1424 printval = max(printval, im->gdes[vidx].data[ii]);
1425 break;
1426 case CF_LAST:
1427 printval = im->gdes[vidx].data[ii];
1428 }
1429 }
1430 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1431 if (validsteps > 1) {
1432 printval = (printval / validsteps);
1433 }
1434 }
1435 } /* prepare printval */
1437 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1438 /* Magfact is set to -1 upon entry to print_calc. If it
1439 * is still less than 0, then we need to run auto_scale.
1440 * Otherwise, put the value into the correct units. If
1441 * the value is 0, then do not set the symbol or magnification
1442 * so next the calculation will be performed again. */
1443 if (magfact < 0.0) {
1444 auto_scale(im, &printval, &si_symb, &magfact);
1445 if (printval == 0.0)
1446 magfact = -1.0;
1447 } else {
1448 printval /= magfact;
1449 }
1450 *(++percent_s) = 's';
1451 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1452 auto_scale(im, &printval, &si_symb, &magfact);
1453 }
1455 if (im->gdes[i].gf == GF_PRINT) {
1456 (*prdata)[prlines - 2] =
1457 malloc((FMT_LEG_LEN + 2) * sizeof(char));
1458 (*prdata)[prlines - 1] = NULL;
1459 if (im->gdes[i].strftm) {
1460 strftime((*prdata)[prlines - 2], FMT_LEG_LEN,
1461 im->gdes[i].format, &tmvdef);
1462 } else {
1463 if (bad_format(im->gdes[i].format)) {
1464 rrd_set_error("bad format for PRINT in '%s'",
1465 im->gdes[i].format);
1466 return -1;
1467 }
1468 #ifdef HAVE_SNPRINTF
1469 snprintf((*prdata)[prlines - 2], FMT_LEG_LEN,
1470 im->gdes[i].format, printval, si_symb);
1471 #else
1472 sprintf((*prdata)[prlines - 2], im->gdes[i].format,
1473 printval, si_symb);
1474 #endif
1475 }
1476 } else {
1477 /* GF_GPRINT */
1479 if (im->gdes[i].strftm) {
1480 strftime(im->gdes[i].legend, FMT_LEG_LEN,
1481 im->gdes[i].format, &tmvdef);
1482 } else {
1483 if (bad_format(im->gdes[i].format)) {
1484 rrd_set_error("bad format for GPRINT in '%s'",
1485 im->gdes[i].format);
1486 return -1;
1487 }
1488 #ifdef HAVE_SNPRINTF
1489 snprintf(im->gdes[i].legend, FMT_LEG_LEN - 2,
1490 im->gdes[i].format, printval, si_symb);
1491 #else
1492 sprintf(im->gdes[i].legend, im->gdes[i].format, printval,
1493 si_symb);
1494 #endif
1495 }
1496 graphelement = 1;
1497 }
1498 break;
1499 case GF_LINE:
1500 case GF_AREA:
1501 case GF_TICK:
1502 graphelement = 1;
1503 break;
1504 case GF_HRULE:
1505 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1506 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1507 };
1508 graphelement = 1;
1509 break;
1510 case GF_VRULE:
1511 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1512 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1513 };
1514 graphelement = 1;
1515 break;
1516 case GF_COMMENT:
1517 case GF_DEF:
1518 case GF_CDEF:
1519 case GF_VDEF:
1520 #ifdef WITH_PIECHART
1521 case GF_PART:
1522 #endif
1523 case GF_SHIFT:
1524 case GF_XPORT:
1525 break;
1526 case GF_STACK:
1527 rrd_set_error
1528 ("STACK should already be turned into LINE or AREA here");
1529 return -1;
1530 break;
1531 }
1532 }
1533 return graphelement;
1534 }
1537 /* place legends with color spots */
1538 int leg_place(
1539 image_desc_t * im)
1540 {
1541 /* graph labels */
1542 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1543 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1544 int fill = 0, fill_last;
1545 int leg_c = 0;
1546 int leg_x = border, leg_y = im->yimg;
1547 int leg_y_prev = im->yimg;
1548 int leg_cc;
1549 int glue = 0;
1550 int i, ii, mark = 0;
1551 char prt_fctn; /*special printfunctions */
1552 int *legspace;
1554 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1555 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1556 rrd_set_error("malloc for legspace");
1557 return -1;
1558 }
1560 for (i = 0; i < im->gdes_c; i++) {
1561 fill_last = fill;
1563 /* hid legends for rules which are not displayed */
1565 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1566 if (im->gdes[i].gf == GF_HRULE &&
1567 (im->gdes[i].yrule < im->minval
1568 || im->gdes[i].yrule > im->maxval))
1569 im->gdes[i].legend[0] = '\0';
1571 if (im->gdes[i].gf == GF_VRULE &&
1572 (im->gdes[i].xrule < im->start
1573 || im->gdes[i].xrule > im->end))
1574 im->gdes[i].legend[0] = '\0';
1575 }
1577 leg_cc = strlen(im->gdes[i].legend);
1579 /* is there a controle code ant the end of the legend string ? */
1580 /* and it is not a tab \\t */
1581 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\'
1582 && im->gdes[i].legend[leg_cc - 1] != 't') {
1583 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1584 leg_cc -= 2;
1585 im->gdes[i].legend[leg_cc] = '\0';
1586 } else {
1587 prt_fctn = '\0';
1588 }
1589 /* only valid control codes */
1590 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1591 prt_fctn != 'r' &&
1592 prt_fctn != 'j' &&
1593 prt_fctn != 'c' &&
1594 prt_fctn != 's' &&
1595 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1596 free(legspace);
1597 rrd_set_error("Unknown control code at the end of '%s\\%c'",
1598 im->gdes[i].legend, prt_fctn);
1599 return -1;
1601 }
1603 /* remove exess space */
1604 if (prt_fctn == 'n') {
1605 prt_fctn = 'l';
1606 }
1608 while (prt_fctn == 'g' &&
1609 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1610 leg_cc--;
1611 im->gdes[i].legend[leg_cc] = '\0';
1612 }
1613 if (leg_cc != 0) {
1614 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1616 if (fill > 0) {
1617 /* no interleg space if string ends in \g */
1618 fill += legspace[i];
1619 }
1620 fill += gfx_get_text_width(im->canvas, fill + border,
1621 im->text_prop[TEXT_PROP_LEGEND].
1622 font,
1623 im->text_prop[TEXT_PROP_LEGEND].
1624 size, im->tabwidth,
1625 im->gdes[i].legend, 0);
1626 leg_c++;
1627 } else {
1628 legspace[i] = 0;
1629 }
1630 /* who said there was a special tag ... ? */
1631 if (prt_fctn == 'g') {
1632 prt_fctn = '\0';
1633 }
1634 if (prt_fctn == '\0') {
1635 if (i == im->gdes_c - 1)
1636 prt_fctn = 'l';
1638 /* is it time to place the legends ? */
1639 if (fill > im->ximg - 2 * border) {
1640 if (leg_c > 1) {
1641 /* go back one */
1642 i--;
1643 fill = fill_last;
1644 leg_c--;
1645 prt_fctn = 'j';
1646 } else {
1647 prt_fctn = 'l';
1648 }
1650 }
1651 }
1654 if (prt_fctn != '\0') {
1655 leg_x = border;
1656 if (leg_c >= 2 && prt_fctn == 'j') {
1657 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1658 } else {
1659 glue = 0;
1660 }
1661 if (prt_fctn == 'c')
1662 leg_x = (im->ximg - fill) / 2.0;
1663 if (prt_fctn == 'r')
1664 leg_x = im->ximg - fill - border;
1666 for (ii = mark; ii <= i; ii++) {
1667 if (im->gdes[ii].legend[0] == '\0')
1668 continue; /* skip empty legends */
1669 im->gdes[ii].leg_x = leg_x;
1670 im->gdes[ii].leg_y = leg_y;
1671 leg_x +=
1672 gfx_get_text_width(im->canvas, leg_x,
1673 im->text_prop[TEXT_PROP_LEGEND].
1674 font,
1675 im->text_prop[TEXT_PROP_LEGEND].
1676 size, im->tabwidth,
1677 im->gdes[ii].legend, 0)
1678 + legspace[ii]
1679 + glue;
1680 }
1681 leg_y_prev = leg_y;
1682 /* only add y space if there was text on the line */
1683 if (leg_x > border || prt_fctn == 's')
1684 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1685 if (prt_fctn == 's')
1686 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1687 fill = 0;
1688 leg_c = 0;
1689 mark = ii;
1690 }
1691 }
1692 im->yimg = leg_y_prev;
1693 /* if we did place some legends we have to add vertical space */
1694 if (leg_y != im->yimg) {
1695 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1696 }
1697 free(legspace);
1698 }
1699 return 0;
1700 }
1702 /* create a grid on the graph. it determines what to do
1703 from the values of xsize, start and end */
1705 /* the xaxis labels are determined from the number of seconds per pixel
1706 in the requested graph */
1710 int calc_horizontal_grid(
1711 image_desc_t * im)
1712 {
1713 double range;
1714 double scaledrange;
1715 int pixel, i;
1716 int gridind = 0;
1717 int decimals, fractionals;
1719 im->ygrid_scale.labfact = 2;
1720 range = im->maxval - im->minval;
1721 scaledrange = range / im->magfact;
1723 /* does the scale of this graph make it impossible to put lines
1724 on it? If so, give up. */
1725 if (isnan(scaledrange)) {
1726 return 0;
1727 }
1729 /* find grid spaceing */
1730 pixel = 1;
1731 if (isnan(im->ygridstep)) {
1732 if (im->extra_flags & ALTYGRID) {
1733 /* find the value with max number of digits. Get number of digits */
1734 decimals =
1735 ceil(log10
1736 (max(fabs(im->maxval), fabs(im->minval)) *
1737 im->viewfactor / im->magfact));
1738 if (decimals <= 0) /* everything is small. make place for zero */
1739 decimals = 1;
1741 im->ygrid_scale.gridstep =
1742 pow((double) 10,
1743 floor(log10(range * im->viewfactor / im->magfact))) /
1744 im->viewfactor * im->magfact;
1746 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1747 im->ygrid_scale.gridstep = 0.1;
1748 /* should have at least 5 lines but no more then 15 */
1749 if (range / im->ygrid_scale.gridstep < 5)
1750 im->ygrid_scale.gridstep /= 10;
1751 if (range / im->ygrid_scale.gridstep > 15)
1752 im->ygrid_scale.gridstep *= 10;
1753 if (range / im->ygrid_scale.gridstep > 5) {
1754 im->ygrid_scale.labfact = 1;
1755 if (range / im->ygrid_scale.gridstep > 8)
1756 im->ygrid_scale.labfact = 2;
1757 } else {
1758 im->ygrid_scale.gridstep /= 5;
1759 im->ygrid_scale.labfact = 5;
1760 }
1761 fractionals =
1762 floor(log10
1763 (im->ygrid_scale.gridstep *
1764 (double) im->ygrid_scale.labfact * im->viewfactor /
1765 im->magfact));
1766 if (fractionals < 0) { /* small amplitude. */
1767 int len = decimals - fractionals + 1;
1769 if (im->unitslength < len + 2)
1770 im->unitslength = len + 2;
1771 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
1772 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1773 } else {
1774 int len = decimals + 1;
1776 if (im->unitslength < len + 2)
1777 im->unitslength = len + 2;
1778 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
1779 (im->symbol != ' ' ? " %c" : ""));
1780 }
1781 } else {
1782 for (i = 0; ylab[i].grid > 0; i++) {
1783 pixel = im->ysize / (scaledrange / ylab[i].grid);
1784 gridind = i;
1785 if (pixel > 7)
1786 break;
1787 }
1789 for (i = 0; i < 4; i++) {
1790 if (pixel * ylab[gridind].lfac[i] >=
1791 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1792 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1793 break;
1794 }
1795 }
1797 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1798 }
1799 } else {
1800 im->ygrid_scale.gridstep = im->ygridstep;
1801 im->ygrid_scale.labfact = im->ylabfact;
1802 }
1803 return 1;
1804 }
1806 int draw_horizontal_grid(
1807 image_desc_t * im)
1808 {
1809 int i;
1810 double scaledstep;
1811 char graph_label[100];
1812 int nlabels = 0;
1813 double X0 = im->xorigin;
1814 double X1 = im->xorigin + im->xsize;
1816 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1817 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1818 double MaxY;
1820 scaledstep =
1821 im->ygrid_scale.gridstep / (double) im->magfact *
1822 (double) im->viewfactor;
1823 MaxY = scaledstep * (double) egrid;
1824 for (i = sgrid; i <= egrid; i++) {
1825 double Y0 = ytr(im, im->ygrid_scale.gridstep * i);
1826 double YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
1828 if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
1829 && floor(Y0 + 0.5) <= im->yorigin) {
1830 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1831 with the chosen settings. Add a label if required by settings, or if
1832 there is only one label so far and the next grid line is out of bounds. */
1833 if (i % im->ygrid_scale.labfact == 0
1834 || (nlabels == 1
1835 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1836 if (im->symbol == ' ') {
1837 if (im->extra_flags & ALTYGRID) {
1838 sprintf(graph_label, im->ygrid_scale.labfmt,
1839 scaledstep * (double) i);
1840 } else {
1841 if (MaxY < 10) {
1842 sprintf(graph_label, "%4.1f",
1843 scaledstep * (double) i);
1844 } else {
1845 sprintf(graph_label, "%4.0f",
1846 scaledstep * (double) i);
1847 }
1848 }
1849 } else {
1850 char sisym = (i == 0 ? ' ' : im->symbol);
1852 if (im->extra_flags & ALTYGRID) {
1853 sprintf(graph_label, im->ygrid_scale.labfmt,
1854 scaledstep * (double) i, sisym);
1855 } else {
1856 if (MaxY < 10) {
1857 sprintf(graph_label, "%4.1f %c",
1858 scaledstep * (double) i, sisym);
1859 } else {
1860 sprintf(graph_label, "%4.0f %c",
1861 scaledstep * (double) i, sisym);
1862 }
1863 }
1864 }
1865 nlabels++;
1867 gfx_new_text(im->canvas,
1868 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
1869 im->graph_col[GRC_FONT],
1870 im->text_prop[TEXT_PROP_AXIS].font,
1871 im->text_prop[TEXT_PROP_AXIS].size,
1872 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1873 graph_label);
1874 gfx_new_dashed_line(im->canvas,
1875 X0 - 2, Y0,
1876 X1 + 2, Y0,
1877 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1878 im->grid_dash_on, im->grid_dash_off);
1880 } else if (!(im->extra_flags & NOMINOR)) {
1881 gfx_new_dashed_line(im->canvas,
1882 X0 - 1, Y0,
1883 X1 + 1, Y0,
1884 GRIDWIDTH, im->graph_col[GRC_GRID],
1885 im->grid_dash_on, im->grid_dash_off);
1887 }
1888 }
1889 }
1890 return 1;
1891 }
1893 /* this is frexp for base 10 */
1894 double frexp10(
1895 double,
1896 double *);
1897 double frexp10(
1898 double x,
1899 double *e)
1900 {
1901 double mnt;
1902 int iexp;
1904 iexp = floor(log(fabs(x)) / log(10));
1905 mnt = x / pow(10.0, iexp);
1906 if (mnt >= 10.0) {
1907 iexp++;
1908 mnt = x / pow(10.0, iexp);
1909 }
1910 *e = iexp;
1911 return mnt;
1912 }
1914 static int AlmostEqual2sComplement(
1915 float A,
1916 float B,
1917 int maxUlps)
1918 {
1920 int aInt = *(int *) &A;
1921 int bInt = *(int *) &B;
1922 int intDiff;
1924 /* Make sure maxUlps is non-negative and small enough that the
1925 default NAN won't compare as equal to anything. */
1927 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1929 /* Make aInt lexicographically ordered as a twos-complement int */
1931 if (aInt < 0)
1932 aInt = 0x80000000l - aInt;
1934 /* Make bInt lexicographically ordered as a twos-complement int */
1936 if (bInt < 0)
1937 bInt = 0x80000000l - bInt;
1939 intDiff = abs(aInt - bInt);
1941 if (intDiff <= maxUlps)
1942 return 1;
1944 return 0;
1945 }
1947 /* logaritmic horizontal grid */
1948 int horizontal_log_grid(
1949 image_desc_t * im)
1950 {
1951 double yloglab[][10] = {
1952 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1953 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
1954 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
1955 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
1956 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
1957 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
1958 };
1960 int i, j, val_exp, min_exp;
1961 double nex; /* number of decades in data */
1962 double logscale; /* scale in logarithmic space */
1963 int exfrac = 1; /* decade spacing */
1964 int mid = -1; /* row in yloglab for major grid */
1965 double mspac; /* smallest major grid spacing (pixels) */
1966 int flab; /* first value in yloglab to use */
1967 double value, tmp, pre_value;
1968 double X0, X1, Y0;
1969 char graph_label[100];
1971 nex = log10(im->maxval / im->minval);
1972 logscale = im->ysize / nex;
1974 /* major spacing for data with high dynamic range */
1975 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
1976 if (exfrac == 1)
1977 exfrac = 3;
1978 else
1979 exfrac += 3;
1980 }
1982 /* major spacing for less dynamic data */
1983 do {
1984 /* search best row in yloglab */
1985 mid++;
1986 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
1987 mspac = logscale * log10(10.0 / yloglab[mid][i]);
1988 } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
1989 && yloglab[mid][0] > 0);
1990 if (mid)
1991 mid--;
1993 /* find first value in yloglab */
1994 for (flab = 0;
1995 yloglab[mid][flab] < 10
1996 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
1997 if (yloglab[mid][flab] == 10.0) {
1998 tmp += 1.0;
1999 flab = 0;
2000 }
2001 val_exp = tmp;
2002 if (val_exp % exfrac)
2003 val_exp += abs(-val_exp % exfrac);
2005 X0 = im->xorigin;
2006 X1 = im->xorigin + im->xsize;
2008 /* draw grid */
2009 pre_value = DNAN;
2010 while (1) {
2012 value = yloglab[mid][flab] * pow(10.0, val_exp);
2013 if (AlmostEqual2sComplement(value, pre_value, 4))
2014 break; /* it seems we are not converging */
2016 pre_value = value;
2018 Y0 = ytr(im, value);
2019 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2020 break;
2022 /* major grid line */
2023 gfx_new_dashed_line(im->canvas,
2024 X0 - 2, Y0,
2025 X1 + 2, Y0,
2026 MGRIDWIDTH, im->graph_col[GRC_MGRID],
2027 im->grid_dash_on, im->grid_dash_off);
2029 /* label */
2030 if (im->extra_flags & FORCE_UNITS_SI) {
2031 int scale;
2032 double pvalue;
2033 char symbol;
2035 scale = floor(val_exp / 3.0);
2036 if (value >= 1.0)
2037 pvalue = pow(10.0, val_exp % 3);
2038 else
2039 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2040 pvalue *= yloglab[mid][flab];
2042 if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
2043 ((scale + si_symbcenter) >= 0))
2044 symbol = si_symbol[scale + si_symbcenter];
2045 else
2046 symbol = '?';
2048 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2049 } else
2050 sprintf(graph_label, "%3.0e", value);
2051 gfx_new_text(im->canvas,
2052 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
2053 im->graph_col[GRC_FONT],
2054 im->text_prop[TEXT_PROP_AXIS].font,
2055 im->text_prop[TEXT_PROP_AXIS].size,
2056 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
2057 graph_label);
2059 /* minor grid */
2060 if (mid < 4 && exfrac == 1) {
2061 /* find first and last minor line behind current major line
2062 * i is the first line and j tha last */
2063 if (flab == 0) {
2064 min_exp = val_exp - 1;
2065 for (i = 1; yloglab[mid][i] < 10.0; i++);
2066 i = yloglab[mid][i - 1] + 1;
2067 j = 10;
2068 } else {
2069 min_exp = val_exp;
2070 i = yloglab[mid][flab - 1] + 1;
2071 j = yloglab[mid][flab];
2072 }
2074 /* draw minor lines below current major line */
2075 for (; i < j; i++) {
2077 value = i * pow(10.0, min_exp);
2078 if (value < im->minval)
2079 continue;
2081 Y0 = ytr(im, value);
2082 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2083 break;
2085 /* draw lines */
2086 gfx_new_dashed_line(im->canvas,
2087 X0 - 1, Y0,
2088 X1 + 1, Y0,
2089 GRIDWIDTH, im->graph_col[GRC_GRID],
2090 im->grid_dash_on, im->grid_dash_off);
2091 }
2092 } else if (exfrac > 1) {
2093 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2094 value = pow(10.0, i);
2095 if (value < im->minval)
2096 continue;
2098 Y0 = ytr(im, value);
2099 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2100 break;
2102 /* draw lines */
2103 gfx_new_dashed_line(im->canvas,
2104 X0 - 1, Y0,
2105 X1 + 1, Y0,
2106 GRIDWIDTH, im->graph_col[GRC_GRID],
2107 im->grid_dash_on, im->grid_dash_off);
2108 }
2109 }
2111 /* next decade */
2112 if (yloglab[mid][++flab] == 10.0) {
2113 flab = 0;
2114 val_exp += exfrac;
2115 }
2116 }
2118 /* draw minor lines after highest major line */
2119 if (mid < 4 && exfrac == 1) {
2120 /* find first and last minor line below current major line
2121 * i is the first line and j tha last */
2122 if (flab == 0) {
2123 min_exp = val_exp - 1;
2124 for (i = 1; yloglab[mid][i] < 10.0; i++);
2125 i = yloglab[mid][i - 1] + 1;
2126 j = 10;
2127 } else {
2128 min_exp = val_exp;
2129 i = yloglab[mid][flab - 1] + 1;
2130 j = yloglab[mid][flab];
2131 }
2133 /* draw minor lines below current major line */
2134 for (; i < j; i++) {
2136 value = i * pow(10.0, min_exp);
2137 if (value < im->minval)
2138 continue;
2140 Y0 = ytr(im, value);
2141 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2142 break;
2144 /* draw lines */
2145 gfx_new_dashed_line(im->canvas,
2146 X0 - 1, Y0,
2147 X1 + 1, Y0,
2148 GRIDWIDTH, im->graph_col[GRC_GRID],
2149 im->grid_dash_on, im->grid_dash_off);
2150 }
2151 }
2152 /* fancy minor gridlines */
2153 else if (exfrac > 1) {
2154 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2155 value = pow(10.0, i);
2156 if (value < im->minval)
2157 continue;
2159 Y0 = ytr(im, value);
2160 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2161 break;
2163 /* draw lines */
2164 gfx_new_dashed_line(im->canvas,
2165 X0 - 1, Y0,
2166 X1 + 1, Y0,
2167 GRIDWIDTH, im->graph_col[GRC_GRID],
2168 im->grid_dash_on, im->grid_dash_off);
2169 }
2170 }
2172 return 1;
2173 }
2176 void vertical_grid(
2177 image_desc_t * im)
2178 {
2179 int xlab_sel; /* which sort of label and grid ? */
2180 time_t ti, tilab, timajor;
2181 long factor;
2182 char graph_label[100];
2183 double X0, Y0, Y1; /* points for filled graph and more */
2184 struct tm tm;
2186 /* the type of time grid is determined by finding
2187 the number of seconds per pixel in the graph */
2190 if (im->xlab_user.minsec == -1) {
2191 factor = (im->end - im->start) / im->xsize;
2192 xlab_sel = 0;
2193 while (xlab[xlab_sel + 1].minsec != -1
2194 && xlab[xlab_sel + 1].minsec <= factor) {
2195 xlab_sel++;
2196 } /* pick the last one */
2197 while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
2198 && xlab[xlab_sel].length > (im->end - im->start)) {
2199 xlab_sel--;
2200 } /* go back to the smallest size */
2201 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2202 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2203 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2204 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2205 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2206 im->xlab_user.labst = xlab[xlab_sel].labst;
2207 im->xlab_user.precis = xlab[xlab_sel].precis;
2208 im->xlab_user.stst = xlab[xlab_sel].stst;
2209 }
2211 /* y coords are the same for every line ... */
2212 Y0 = im->yorigin;
2213 Y1 = im->yorigin - im->ysize;
2216 /* paint the minor grid */
2217 if (!(im->extra_flags & NOMINOR)) {
2218 for (ti = find_first_time(im->start,
2219 im->xlab_user.gridtm,
2220 im->xlab_user.gridst),
2221 timajor = find_first_time(im->start,
2222 im->xlab_user.mgridtm,
2223 im->xlab_user.mgridst);
2224 ti < im->end;
2225 ti =
2226 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2227 ) {
2228 /* are we inside the graph ? */
2229 if (ti < im->start || ti > im->end)
2230 continue;
2231 while (timajor < ti) {
2232 timajor = find_next_time(timajor,
2233 im->xlab_user.mgridtm,
2234 im->xlab_user.mgridst);
2235 }
2236 if (ti == timajor)
2237 continue; /* skip as falls on major grid line */
2238 X0 = xtr(im, ti);
2239 gfx_new_dashed_line(im->canvas, X0, Y0 + 1, X0, Y1 - 1, GRIDWIDTH,
2240 im->graph_col[GRC_GRID],
2241 im->grid_dash_on, im->grid_dash_off);
2243 }
2244 }
2246 /* paint the major grid */
2247 for (ti = find_first_time(im->start,
2248 im->xlab_user.mgridtm,
2249 im->xlab_user.mgridst);
2250 ti < im->end;
2251 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2252 ) {
2253 /* are we inside the graph ? */
2254 if (ti < im->start || ti > im->end)
2255 continue;
2256 X0 = xtr(im, ti);
2257 gfx_new_dashed_line(im->canvas, X0, Y0 + 3, X0, Y1 - 2, MGRIDWIDTH,
2258 im->graph_col[GRC_MGRID],
2259 im->grid_dash_on, im->grid_dash_off);
2261 }
2262 /* paint the labels below the graph */
2263 for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
2264 im->xlab_user.labtm,
2265 im->xlab_user.labst);
2266 ti <= im->end - im->xlab_user.precis / 2;
2267 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2268 ) {
2269 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2270 /* are we inside the graph ? */
2271 if (tilab < im->start || tilab > im->end)
2272 continue;
2274 #if HAVE_STRFTIME
2275 localtime_r(&tilab, &tm);
2276 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2277 #else
2278 # error "your libc has no strftime I guess we'll abort the exercise here."
2279 #endif
2280 gfx_new_text(im->canvas,
2281 xtr(im, tilab),
2282 Y0 + im->text_prop[TEXT_PROP_AXIS].size * 1.4 + 5,
2283 im->graph_col[GRC_FONT],
2284 im->text_prop[TEXT_PROP_AXIS].font,
2285 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
2286 GFX_H_CENTER, GFX_V_BOTTOM, graph_label);
2288 }
2290 }
2293 void axis_paint(
2294 image_desc_t * im)
2295 {
2296 /* draw x and y axis */
2297 /* gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2298 im->xorigin+im->xsize,im->yorigin-im->ysize,
2299 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2301 gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2302 im->xorigin+im->xsize,im->yorigin-im->ysize,
2303 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2305 gfx_new_line(im->canvas, im->xorigin - 4, im->yorigin,
2306 im->xorigin + im->xsize + 4, im->yorigin,
2307 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2309 gfx_new_line(im->canvas, im->xorigin, im->yorigin + 4,
2310 im->xorigin, im->yorigin - im->ysize - 4,
2311 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2314 /* arrow for X and Y axis direction */
2315 gfx_new_area(im->canvas, im->xorigin + im->xsize + 2, im->yorigin - 2, im->xorigin + im->xsize + 2, im->yorigin + 3, im->xorigin + im->xsize + 7, im->yorigin + 0.5, /* LINEOFFSET */
2316 im->graph_col[GRC_ARROW]);
2318 gfx_new_area(im->canvas, im->xorigin - 2, im->yorigin - im->ysize - 2, im->xorigin + 3, im->yorigin - im->ysize - 2, im->xorigin + 0.5, im->yorigin - im->ysize - 7, /* LINEOFFSET */
2319 im->graph_col[GRC_ARROW]);
2321 }
2323 void grid_paint(
2324 image_desc_t * im)
2325 {
2326 long i;
2327 int res = 0;
2328 double X0, Y0; /* points for filled graph and more */
2329 gfx_node_t *node;
2331 /* draw 3d border */
2332 node = gfx_new_area(im->canvas, 0, im->yimg,
2333 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2334 gfx_add_point(node, im->ximg - 2, 2);
2335 gfx_add_point(node, im->ximg, 0);
2336 gfx_add_point(node, 0, 0);
2337 /* gfx_add_point( node , 0,im->yimg ); */
2339 node = gfx_new_area(im->canvas, 2, im->yimg - 2,
2340 im->ximg - 2, im->yimg - 2,
2341 im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2342 gfx_add_point(node, im->ximg, 0);
2343 gfx_add_point(node, im->ximg, im->yimg);
2344 gfx_add_point(node, 0, im->yimg);
2345 /* gfx_add_point( node , 0,im->yimg ); */
2348 if (im->draw_x_grid == 1)
2349 vertical_grid(im);
2351 if (im->draw_y_grid == 1) {
2352 if (im->logarithmic) {
2353 res = horizontal_log_grid(im);
2354 } else {
2355 res = draw_horizontal_grid(im);
2356 }
2358 /* dont draw horizontal grid if there is no min and max val */
2359 if (!res) {
2360 char *nodata = "No Data found";
2362 gfx_new_text(im->canvas, im->ximg / 2,
2363 (2 * im->yorigin - im->ysize) / 2,
2364 im->graph_col[GRC_FONT],
2365 im->text_prop[TEXT_PROP_AXIS].font,
2366 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2367 0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2368 }
2369 }
2371 /* yaxis unit description */
2372 gfx_new_text(im->canvas,
2373 10, (im->yorigin - im->ysize / 2),
2374 im->graph_col[GRC_FONT],
2375 im->text_prop[TEXT_PROP_UNIT].font,
2376 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2377 RRDGRAPH_YLEGEND_ANGLE,
2378 GFX_H_LEFT, GFX_V_CENTER, im->ylegend);
2380 /* graph title */
2381 gfx_new_text(im->canvas,
2382 im->ximg / 2, im->text_prop[TEXT_PROP_TITLE].size * 1.3 + 4,
2383 im->graph_col[GRC_FONT],
2384 im->text_prop[TEXT_PROP_TITLE].font,
2385 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2386 GFX_H_CENTER, GFX_V_CENTER, im->title);
2387 /* rrdtool 'logo' */
2388 gfx_new_text(im->canvas,
2389 im->ximg - 7, 7,
2390 (im->graph_col[GRC_FONT] & 0xffffff00) | 0x00000044,
2391 im->text_prop[TEXT_PROP_AXIS].font,
2392 5.5, im->tabwidth, 270,
2393 GFX_H_RIGHT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2395 /* graph watermark */
2396 if (im->watermark[0] != '\0') {
2397 gfx_new_text(im->canvas,
2398 im->ximg / 2, im->yimg - 6,
2399 (im->graph_col[GRC_FONT] & 0xffffff00) | 0x00000044,
2400 im->text_prop[TEXT_PROP_AXIS].font,
2401 5.5, im->tabwidth, 0,
2402 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2403 }
2405 /* graph labels */
2406 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2407 for (i = 0; i < im->gdes_c; i++) {
2408 if (im->gdes[i].legend[0] == '\0')
2409 continue;
2411 /* im->gdes[i].leg_y is the bottom of the legend */
2412 X0 = im->gdes[i].leg_x;
2413 Y0 = im->gdes[i].leg_y;
2414 gfx_new_text(im->canvas, X0, Y0,
2415 im->graph_col[GRC_FONT],
2416 im->text_prop[TEXT_PROP_LEGEND].font,
2417 im->text_prop[TEXT_PROP_LEGEND].size,
2418 im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2419 im->gdes[i].legend);
2420 /* The legend for GRAPH items starts with "M " to have
2421 enough space for the box */
2422 if (im->gdes[i].gf != GF_PRINT &&
2423 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2424 int boxH, boxV;
2426 boxH = gfx_get_text_width(im->canvas, 0,
2427 im->text_prop[TEXT_PROP_LEGEND].
2428 font,
2429 im->text_prop[TEXT_PROP_LEGEND].
2430 size, im->tabwidth, "o", 0) * 1.2;
2431 boxV = boxH * 1.1;
2433 /* make sure transparent colors show up the same way as in the graph */
2434 node = gfx_new_area(im->canvas,
2435 X0, Y0 - boxV,
2436 X0, Y0,
2437 X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2438 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2440 node = gfx_new_area(im->canvas,
2441 X0, Y0 - boxV,
2442 X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2443 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2444 node = gfx_new_line(im->canvas,
2445 X0, Y0 - boxV,
2446 X0, Y0, 1.0, im->graph_col[GRC_FRAME]);
2447 gfx_add_point(node, X0 + boxH, Y0);
2448 gfx_add_point(node, X0 + boxH, Y0 - boxV);
2449 gfx_close_path(node);
2450 }
2451 }
2452 }
2453 }
2456 /*****************************************************
2457 * lazy check make sure we rely need to create this graph
2458 *****************************************************/
2460 int lazy_check(
2461 image_desc_t * im)
2462 {
2463 FILE *fd = NULL;
2464 int size = 1;
2465 struct stat imgstat;
2467 if (im->lazy == 0)
2468 return 0; /* no lazy option */
2469 if (stat(im->graphfile, &imgstat) != 0)
2470 return 0; /* can't stat */
2471 /* one pixel in the existing graph is more then what we would
2472 change here ... */
2473 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2474 return 0;
2475 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2476 return 0; /* the file does not exist */
2477 switch (im->canvas->imgformat) {
2478 case IF_PNG:
2479 size = PngSize(fd, &(im->ximg), &(im->yimg));
2480 break;
2481 default:
2482 size = 1;
2483 }
2484 fclose(fd);
2485 return size;
2486 }
2488 #ifdef WITH_PIECHART
2489 void pie_part(
2490 image_desc_t * im,
2491 gfx_color_t color,
2492 double PieCenterX,
2493 double PieCenterY,
2494 double Radius,
2495 double startangle,
2496 double endangle)
2497 {
2498 gfx_node_t *node;
2499 double angle;
2500 double step = M_PI / 50; /* Number of iterations for the circle;
2501 ** 10 is definitely too low, more than
2502 ** 50 seems to be overkill
2503 */
2505 /* Strange but true: we have to work clockwise or else
2506 ** anti aliasing nor transparency don't work.
2507 **
2508 ** This test is here to make sure we do it right, also
2509 ** this makes the for...next loop more easy to implement.
2510 ** The return will occur if the user enters a negative number
2511 ** (which shouldn't be done according to the specs) or if the
2512 ** programmers do something wrong (which, as we all know, never
2513 ** happens anyway :)
2514 */
2515 if (endangle < startangle)
2516 return;
2518 /* Hidden feature: Radius decreases each full circle */
2519 angle = startangle;
2520 while (angle >= 2 * M_PI) {
2521 angle -= 2 * M_PI;
2522 Radius *= 0.8;
2523 }
2525 node = gfx_new_area(im->canvas,
2526 PieCenterX + sin(startangle) * Radius,
2527 PieCenterY - cos(startangle) * Radius,
2528 PieCenterX,
2529 PieCenterY,
2530 PieCenterX + sin(endangle) * Radius,
2531 PieCenterY - cos(endangle) * Radius, color);
2532 for (angle = endangle; angle - startangle >= step; angle -= step) {
2533 gfx_add_point(node,
2534 PieCenterX + sin(angle) * Radius,
2535 PieCenterY - cos(angle) * Radius);
2536 }
2537 }
2539 #endif
2541 int graph_size_location(
2542 image_desc_t * im,
2543 int elements
2544 #ifdef WITH_PIECHART
2545 ,
2546 int piechart
2547 #endif
2548 )
2549 {
2550 /* The actual size of the image to draw is determined from
2551 ** several sources. The size given on the command line is
2552 ** the graph area but we need more as we have to draw labels
2553 ** and other things outside the graph area
2554 */
2556 /* +-+-------------------------------------------+
2557 ** |l|.................title.....................|
2558 ** |e+--+-------------------------------+--------+
2559 ** |b| b| | |
2560 ** |a| a| | pie |
2561 ** |l| l| main graph area | chart |
2562 ** |.| .| | area |
2563 ** |t| y| | |
2564 ** |r+--+-------------------------------+--------+
2565 ** |e| | x-axis labels | |
2566 ** |v+--+-------------------------------+--------+
2567 ** | |..............legends......................|
2568 ** +-+-------------------------------------------+
2569 ** | watermark |
2570 ** +---------------------------------------------+
2571 */
2572 int Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2573 #ifdef WITH_PIECHART
2574 Xpie = 0, Ypie = 0,
2575 #endif
2576 Yxlabel = 0,
2577 #if 0
2578 Xlegend = 0, Ylegend = 0,
2579 #endif
2580 Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2582 if (im->extra_flags & ONLY_GRAPH) {
2583 im->xorigin = 0;
2584 im->ximg = im->xsize;
2585 im->yimg = im->ysize;
2586 im->yorigin = im->ysize;
2587 ytr(im, DNAN);
2588 return 0;
2589 }
2591 if (im->ylegend[0] != '\0') {
2592 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2593 }
2596 if (im->title[0] != '\0') {
2597 /* The title is placed "inbetween" two text lines so it
2598 ** automatically has some vertical spacing. The horizontal
2599 ** spacing is added here, on each side.
2600 */
2601 /* don't care for the with of the title
2602 Xtitle = gfx_get_text_width(im->canvas, 0,
2603 im->text_prop[TEXT_PROP_TITLE].font,
2604 im->text_prop[TEXT_PROP_TITLE].size,
2605 im->tabwidth,
2606 im->title, 0) + 2*Xspacing; */
2607 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2608 }
2610 if (elements) {
2611 Xmain = im->xsize;
2612 Ymain = im->ysize;
2613 if (im->draw_x_grid) {
2614 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2615 }
2616 if (im->draw_y_grid || im->forceleftspace) {
2617 Xylabel = gfx_get_text_width(im->canvas, 0,
2618 im->text_prop[TEXT_PROP_AXIS].font,
2619 im->text_prop[TEXT_PROP_AXIS].size,
2620 im->tabwidth,
2621 "0", 0) * im->unitslength;
2622 }
2623 }
2624 #ifdef WITH_PIECHART
2625 if (piechart) {
2626 im->piesize = im->xsize < im->ysize ? im->xsize : im->ysize;
2627 Xpie = im->piesize;
2628 Ypie = im->piesize;
2629 }
2630 #endif
2632 /* Now calculate the total size. Insert some spacing where
2633 desired. im->xorigin and im->yorigin need to correspond
2634 with the lower left corner of the main graph area or, if
2635 this one is not set, the imaginary box surrounding the
2636 pie chart area. */
2638 /* The legend width cannot yet be determined, as a result we
2639 ** have problems adjusting the image to it. For now, we just
2640 ** forget about it at all; the legend will have to fit in the
2641 ** size already allocated.
2642 */
2643 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2645 #ifdef WITH_PIECHART
2646 im->ximg += Xpie;
2647 #endif
2649 if (Xmain)
2650 im->ximg += Xspacing;
2651 #ifdef WITH_PIECHART
2652 if (Xpie)
2653 im->ximg += Xspacing;
2654 #endif
2656 im->xorigin = Xspacing + Xylabel;
2658 /* the length of the title should not influence with width of the graph
2659 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2661 if (Xvertical) { /* unit description */
2662 im->ximg += Xvertical;
2663 im->xorigin += Xvertical;
2664 }
2665 xtr(im, 0);
2667 /* The vertical size is interesting... we need to compare
2668 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2669 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2670 ** in order to start even thinking about Ylegend or Ywatermark.
2671 **
2672 ** Do it in three portions: First calculate the inner part,
2673 ** then do the legend, then adjust the total height of the img,
2674 ** adding space for a watermark if one exists;
2675 */
2677 /* reserve space for main and/or pie */
2679 im->yimg = Ymain + Yxlabel;
2681 #ifdef WITH_PIECHART
2682 if (im->yimg < Ypie)
2683 im->yimg = Ypie;
2684 #endif
2686 im->yorigin = im->yimg - Yxlabel;
2688 /* reserve space for the title *or* some padding above the graph */
2689 if (Ytitle) {
2690 im->yimg += Ytitle;
2691 im->yorigin += Ytitle;
2692 } else {
2693 im->yimg += 1.5 * Yspacing;
2694 im->yorigin += 1.5 * Yspacing;
2695 }
2696 /* reserve space for padding below the graph */
2697 im->yimg += Yspacing;
2699 /* Determine where to place the legends onto the image.
2700 ** Adjust im->yimg to match the space requirements.
2701 */
2702 if (leg_place(im) == -1)
2703 return -1;
2705 if (im->watermark[0] != '\0') {
2706 im->yimg += Ywatermark;
2707 }
2708 #if 0
2709 if (Xlegend > im->ximg) {
2710 im->ximg = Xlegend;
2711 /* reposition Pie */
2712 }
2713 #endif
2715 #ifdef WITH_PIECHART
2716 /* The pie is placed in the upper right hand corner,
2717 ** just below the title (if any) and with sufficient
2718 ** padding.
2719 */
2720 if (elements) {
2721 im->pie_x = im->ximg - Xspacing - Xpie / 2;
2722 im->pie_y = im->yorigin - Ymain + Ypie / 2;
2723 } else {
2724 im->pie_x = im->ximg / 2;
2725 im->pie_y = im->yorigin - Ypie / 2;
2726 }
2727 #endif
2729 ytr(im, DNAN);
2730 return 0;
2731 }
2733 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2734 /* yes we are loosing precision by doing tos with floats instead of doubles
2735 but it seems more stable this way. */
2738 /* draw that picture thing ... */
2739 int graph_paint(
2740 image_desc_t * im,
2741 char ***calcpr)
2742 {
2743 int i, ii;
2744 int lazy = lazy_check(im);
2746 #ifdef WITH_PIECHART
2747 int piechart = 0;
2748 double PieStart = 0.0;
2749 #endif
2750 FILE *fo;
2751 gfx_node_t *node;
2753 double areazero = 0.0;
2754 graph_desc_t *lastgdes = NULL;
2756 /* if we are lazy and there is nothing to PRINT ... quit now */
2757 if (lazy && im->prt_c == 0)
2758 return 0;
2760 /* pull the data from the rrd files ... */
2762 if (data_fetch(im) == -1)
2763 return -1;
2765 /* evaluate VDEF and CDEF operations ... */
2766 if (data_calc(im) == -1)
2767 return -1;
2769 #ifdef WITH_PIECHART
2770 /* check if we need to draw a piechart */
2771 for (i = 0; i < im->gdes_c; i++) {
2772 if (im->gdes[i].gf == GF_PART) {
2773 piechart = 1;
2774 break;
2775 }
2776 }
2777 #endif
2779 /* calculate and PRINT and GPRINT definitions. We have to do it at
2780 * this point because it will affect the length of the legends
2781 * if there are no graph elements we stop here ...
2782 * if we are lazy, try to quit ...
2783 */
2784 i = print_calc(im, calcpr);
2785 if (i < 0)
2786 return -1;
2787 if (((i == 0)
2788 #ifdef WITH_PIECHART
2789 && (piechart == 0)
2790 #endif
2791 ) || lazy)
2792 return 0;
2794 #ifdef WITH_PIECHART
2795 /* If there's only the pie chart to draw, signal this */
2796 if (i == 0)
2797 piechart = 2;
2798 #endif
2800 /* get actual drawing data and find min and max values */
2801 if (data_proc(im) == -1)
2802 return -1;
2804 if (!im->logarithmic) {
2805 si_unit(im);
2806 }
2807 /* identify si magnitude Kilo, Mega Giga ? */
2808 if (!im->rigid && !im->logarithmic)
2809 expand_range(im); /* make sure the upper and lower limit are
2810 sensible values */
2812 if (!calc_horizontal_grid(im))
2813 return -1;
2815 if (im->gridfit)
2816 apply_gridfit(im);
2819 /**************************************************************
2820 *** Calculating sizes and locations became a bit confusing ***
2821 *** so I moved this into a separate function. ***
2822 **************************************************************/
2823 if (graph_size_location(im, i
2824 #ifdef WITH_PIECHART
2825 , piechart
2826 #endif
2827 ) == -1)
2828 return -1;
2830 /* the actual graph is created by going through the individual
2831 graph elements and then drawing them */
2833 node = gfx_new_area(im->canvas,
2834 0, 0,
2835 0, im->yimg,
2836 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
2838 gfx_add_point(node, im->ximg, 0);
2840 #ifdef WITH_PIECHART
2841 if (piechart != 2) {
2842 #endif
2843 node = gfx_new_area(im->canvas,
2844 im->xorigin, im->yorigin,
2845 im->xorigin + im->xsize, im->yorigin,
2846 im->xorigin + im->xsize, im->yorigin - im->ysize,
2847 im->graph_col[GRC_CANVAS]);
2849 gfx_add_point(node, im->xorigin, im->yorigin - im->ysize);
2851 if (im->minval > 0.0)
2852 areazero = im->minval;
2853 if (im->maxval < 0.0)
2854 areazero = im->maxval;
2855 #ifdef WITH_PIECHART
2856 }
2857 #endif
2859 #ifdef WITH_PIECHART
2860 if (piechart) {
2861 pie_part(im, im->graph_col[GRC_CANVAS], im->pie_x, im->pie_y,
2862 im->piesize * 0.5, 0, 2 * M_PI);
2863 }
2864 #endif
2866 for (i = 0; i < im->gdes_c; i++) {
2867 switch (im->gdes[i].gf) {
2868 case GF_CDEF:
2869 case GF_VDEF:
2870 case GF_DEF:
2871 case GF_PRINT:
2872 case GF_GPRINT:
2873 case GF_COMMENT:
2874 case GF_HRULE:
2875 case GF_VRULE:
2876 case GF_XPORT:
2877 case GF_SHIFT:
2878 break;
2879 case GF_TICK:
2880 for (ii = 0; ii < im->xsize; ii++) {
2881 if (!isnan(im->gdes[i].p_data[ii]) &&
2882 im->gdes[i].p_data[ii] != 0.0) {
2883 if (im->gdes[i].yrule > 0) {
2884 gfx_new_line(im->canvas,
2885 im->xorigin + ii, im->yorigin,
2886 im->xorigin + ii,
2887 im->yorigin -
2888 im->gdes[i].yrule * im->ysize, 1.0,
2889 im->gdes[i].col);
2890 } else if (im->gdes[i].yrule < 0) {
2891 gfx_new_line(im->canvas,
2892 im->xorigin + ii,
2893 im->yorigin - im->ysize,
2894 im->xorigin + ii,
2895 im->yorigin - (1 -
2896 im->gdes[i].yrule) *
2897 im->ysize, 1.0, im->gdes[i].col);
2899 }
2900 }
2901 }
2902 break;
2903 case GF_LINE:
2904 case GF_AREA:
2905 /* fix data points at oo and -oo */
2906 for (ii = 0; ii < im->xsize; ii++) {
2907 if (isinf(im->gdes[i].p_data[ii])) {
2908 if (im->gdes[i].p_data[ii] > 0) {
2909 im->gdes[i].p_data[ii] = im->maxval;
2910 } else {
2911 im->gdes[i].p_data[ii] = im->minval;
2912 }
2914 }
2915 } /* for */
2917 /* *******************************************************
2918 a ___. (a,t)
2919 | | ___
2920 ____| | | |
2921 | |___|
2922 -------|--t-1--t--------------------------------
2924 if we know the value at time t was a then
2925 we draw a square from t-1 to t with the value a.
2927 ********************************************************* */
2928 if (im->gdes[i].col != 0x0) {
2929 /* GF_LINE and friend */
2930 if (im->gdes[i].gf == GF_LINE) {
2931 double last_y = 0.0;
2933 node = NULL;
2934 for (ii = 1; ii < im->xsize; ii++) {
2935 if (isnan(im->gdes[i].p_data[ii])
2936 || (im->slopemode == 1
2937 && isnan(im->gdes[i].p_data[ii - 1]))) {
2938 node = NULL;
2939 continue;
2940 }
2941 if (node == NULL) {
2942 last_y = ytr(im, im->gdes[i].p_data[ii]);
2943 if (im->slopemode == 0) {
2944 node = gfx_new_line(im->canvas,
2945 ii - 1 + im->xorigin,
2946 last_y, ii + im->xorigin,
2947 last_y,
2948 im->gdes[i].linewidth,
2949 im->gdes[i].col);
2950 } else {
2951 node = gfx_new_line(im->canvas,
2952 ii - 1 + im->xorigin,
2953 ytr(im,
2954 im->gdes[i].
2955 p_data[ii - 1]),
2956 ii + im->xorigin, last_y,
2957 im->gdes[i].linewidth,
2958 im->gdes[i].col);
2959 }
2960 } else {
2961 double new_y = ytr(im, im->gdes[i].p_data[ii]);
2963 if (im->slopemode == 0
2964 && !AlmostEqual2sComplement(new_y, last_y,
2965 4)) {
2966 gfx_add_point(node, ii - 1 + im->xorigin,
2967 new_y);
2968 };
2969 last_y = new_y;
2970 gfx_add_point(node, ii + im->xorigin, new_y);
2971 };
2973 }
2974 } else {
2975 int idxI = -1;
2976 double *foreY = malloc(sizeof(double) * im->xsize * 2);
2977 double *foreX = malloc(sizeof(double) * im->xsize * 2);
2978 double *backY = malloc(sizeof(double) * im->xsize * 2);
2979 double *backX = malloc(sizeof(double) * im->xsize * 2);
2980 int drawem = 0;
2982 for (ii = 0; ii <= im->xsize; ii++) {
2983 double ybase, ytop;
2985 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
2986 int cntI = 1;
2987 int lastI = 0;
2989 while (cntI < idxI
2990 && AlmostEqual2sComplement(foreY[lastI],
2991 foreY[cntI], 4)
2992 && AlmostEqual2sComplement(foreY[lastI],
2993 foreY[cntI + 1],
2994 4)) {
2995 cntI++;
2996 }
2997 node = gfx_new_area(im->canvas,
2998 backX[0], backY[0],
2999 foreX[0], foreY[0],
3000 foreX[cntI], foreY[cntI],
3001 im->gdes[i].col);
3002 while (cntI < idxI) {
3003 lastI = cntI;
3004 cntI++;
3005 while (cntI < idxI
3006 &&
3007 AlmostEqual2sComplement(foreY[lastI],
3008 foreY[cntI], 4)
3009 &&
3010 AlmostEqual2sComplement(foreY[lastI],
3011 foreY[cntI +
3012 1], 4)) {
3013 cntI++;
3014 }
3015 gfx_add_point(node, foreX[cntI], foreY[cntI]);
3016 }
3017 gfx_add_point(node, backX[idxI], backY[idxI]);
3018 while (idxI > 1) {
3019 lastI = idxI;
3020 idxI--;
3021 while (idxI > 1
3022 &&
3023 AlmostEqual2sComplement(backY[lastI],
3024 backY[idxI], 4)
3025 &&
3026 AlmostEqual2sComplement(backY[lastI],
3027 backY[idxI -
3028 1], 4)) {
3029 idxI--;
3030 }
3031 gfx_add_point(node, backX[idxI], backY[idxI]);
3032 }
3033 idxI = -1;
3034 drawem = 0;
3035 }
3036 if (drawem != 0) {
3037 drawem = 0;
3038 idxI = -1;
3039 }
3040 if (ii == im->xsize)
3041 break;
3043 /* keep things simple for now, just draw these bars
3044 do not try to build a big and complex area */
3047 if (im->slopemode == 0 && ii == 0) {
3048 continue;
3049 }
3050 if (isnan(im->gdes[i].p_data[ii])) {
3051 drawem = 1;
3052 continue;
3053 }
3054 ytop = ytr(im, im->gdes[i].p_data[ii]);
3055 if (lastgdes && im->gdes[i].stack) {
3056 ybase = ytr(im, lastgdes->p_data[ii]);
3057 } else {
3058 ybase = ytr(im, areazero);
3059 }
3060 if (ybase == ytop) {
3061 drawem = 1;
3062 continue;
3063 }
3064 /* every area has to be wound clock-wise,
3065 so we have to make sur base remains base */
3066 if (ybase > ytop) {
3067 double extra = ytop;
3069 ytop = ybase;
3070 ybase = extra;
3071 }
3072 if (im->slopemode == 0) {
3073 backY[++idxI] = ybase - 0.2;
3074 backX[idxI] = ii + im->xorigin - 1;
3075 foreY[idxI] = ytop + 0.2;
3076 foreX[idxI] = ii + im->xorigin - 1;
3077 }
3078 backY[++idxI] = ybase - 0.2;
3079 backX[idxI] = ii + im->xorigin;
3080 foreY[idxI] = ytop + 0.2;
3081 foreX[idxI] = ii + im->xorigin;
3082 }
3083 /* close up any remaining area */
3084 free(foreY);
3085 free(foreX);
3086 free(backY);
3087 free(backX);
3088 } /* else GF_LINE */
3089 }
3090 /* if color != 0x0 */
3091 /* make sure we do not run into trouble when stacking on NaN */
3092 for (ii = 0; ii < im->xsize; ii++) {
3093 if (isnan(im->gdes[i].p_data[ii])) {
3094 if (lastgdes && (im->gdes[i].stack)) {
3095 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3096 } else {
3097 im->gdes[i].p_data[ii] = areazero;
3098 }
3099 }
3100 }
3101 lastgdes = &(im->gdes[i]);
3102 break;
3103 #ifdef WITH_PIECHART
3104 case GF_PART:
3105 if (isnan(im->gdes[i].yrule)) /* fetch variable */
3106 im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
3108 if (finite(im->gdes[i].yrule)) { /* even the fetched var can be NaN */
3109 pie_part(im, im->gdes[i].col,
3110 im->pie_x, im->pie_y, im->piesize * 0.4,
3111 M_PI * 2.0 * PieStart / 100.0,
3112 M_PI * 2.0 * (PieStart + im->gdes[i].yrule) / 100.0);
3113 PieStart += im->gdes[i].yrule;
3114 }
3115 break;
3116 #endif
3117 case GF_STACK:
3118 rrd_set_error
3119 ("STACK should already be turned into LINE or AREA here");
3120 return -1;
3121 break;
3123 } /* switch */
3124 }
3125 #ifdef WITH_PIECHART
3126 if (piechart == 2) {
3127 im->draw_x_grid = 0;
3128 im->draw_y_grid = 0;
3129 }
3130 #endif
3133 /* grid_paint also does the text */
3134 if (!(im->extra_flags & ONLY_GRAPH))
3135 grid_paint(im);
3138 if (!(im->extra_flags & ONLY_GRAPH))
3139 axis_paint(im);
3141 /* the RULES are the last thing to paint ... */
3142 for (i = 0; i < im->gdes_c; i++) {
3144 switch (im->gdes[i].gf) {
3145 case GF_HRULE:
3146 if (im->gdes[i].yrule >= im->minval
3147 && im->gdes[i].yrule <= im->maxval)
3148 gfx_new_line(im->canvas,
3149 im->xorigin, ytr(im, im->gdes[i].yrule),
3150 im->xorigin + im->xsize, ytr(im,
3151 im->gdes[i].yrule),
3152 1.0, im->gdes[i].col);
3153 break;
3154 case GF_VRULE:
3155 if (im->gdes[i].xrule >= im->start
3156 && im->gdes[i].xrule <= im->end)
3157 gfx_new_line(im->canvas,
3158 xtr(im, im->gdes[i].xrule), im->yorigin,
3159 xtr(im, im->gdes[i].xrule),
3160 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3161 break;
3162 default:
3163 break;
3164 }
3165 }
3168 if (strcmp(im->graphfile, "-") == 0) {
3169 fo = im->graphhandle ? im->graphhandle : stdout;
3170 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3171 /* Change translation mode for stdout to BINARY */
3172 _setmode(_fileno(fo), O_BINARY);
3173 #endif
3174 } else {
3175 if ((fo = fopen(im->graphfile, "wb")) == NULL) {
3176 rrd_set_error("Opening '%s' for write: %s", im->graphfile,
3177 rrd_strerror(errno));
3178 return (-1);
3179 }
3180 }
3181 gfx_render(im->canvas, im->ximg, im->yimg, 0x00000000, fo);
3182 if (strcmp(im->graphfile, "-") != 0)
3183 fclose(fo);
3184 return 0;
3185 }
3188 /*****************************************************
3189 * graph stuff
3190 *****************************************************/
3192 int gdes_alloc(
3193 image_desc_t * im)
3194 {
3196 im->gdes_c++;
3197 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3198 * sizeof(graph_desc_t))) ==
3199 NULL) {
3200 rrd_set_error("realloc graph_descs");
3201 return -1;
3202 }
3205 im->gdes[im->gdes_c - 1].step = im->step;
3206 im->gdes[im->gdes_c - 1].step_orig = im->step;
3207 im->gdes[im->gdes_c - 1].stack = 0;
3208 im->gdes[im->gdes_c - 1].linewidth = 0;
3209 im->gdes[im->gdes_c - 1].debug = 0;
3210 im->gdes[im->gdes_c - 1].start = im->start;
3211 im->gdes[im->gdes_c - 1].start_orig = im->start;
3212 im->gdes[im->gdes_c - 1].end = im->end;
3213 im->gdes[im->gdes_c - 1].end_orig = im->end;
3214 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3215 im->gdes[im->gdes_c - 1].data = NULL;
3216 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3217 im->gdes[im->gdes_c - 1].data_first = 0;
3218 im->gdes[im->gdes_c - 1].p_data = NULL;
3219 im->gdes[im->gdes_c - 1].rpnp = NULL;
3220 im->gdes[im->gdes_c - 1].shift = 0;
3221 im->gdes[im->gdes_c - 1].col = 0x0;
3222 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3223 im->gdes[im->gdes_c - 1].format[0] = '\0';
3224 im->gdes[im->gdes_c - 1].strftm = 0;
3225 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3226 im->gdes[im->gdes_c - 1].ds = -1;
3227 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3228 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3229 im->gdes[im->gdes_c - 1].p_data = NULL;
3230 im->gdes[im->gdes_c - 1].yrule = DNAN;
3231 im->gdes[im->gdes_c - 1].xrule = 0;
3232 return 0;
3233 }
3235 /* copies input untill the first unescaped colon is found
3236 or until input ends. backslashes have to be escaped as well */
3237 int scan_for_col(
3238 const char *const input,
3239 int len,
3240 char *const output)
3241 {
3242 int inp, outp = 0;
3244 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3245 if (input[inp] == '\\' &&
3246 input[inp + 1] != '\0' &&
3247 (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3248 output[outp++] = input[++inp];
3249 } else {
3250 output[outp++] = input[inp];
3251 }
3252 }
3253 output[outp] = '\0';
3254 return inp;
3255 }
3257 /* Some surgery done on this function, it became ridiculously big.
3258 ** Things moved:
3259 ** - initializing now in rrd_graph_init()
3260 ** - options parsing now in rrd_graph_options()
3261 ** - script parsing now in rrd_graph_script()
3262 */
3263 int rrd_graph(
3264 int argc,
3265 char **argv,
3266 char ***prdata,
3267 int *xsize,
3268 int *ysize,
3269 FILE * stream,
3270 double *ymin,
3271 double *ymax)
3272 {
3273 image_desc_t im;
3275 rrd_graph_init(&im);
3276 im.graphhandle = stream;
3278 rrd_graph_options(argc, argv, &im);
3279 if (rrd_test_error()) {
3280 im_free(&im);
3281 return -1;
3282 }
3284 if (strlen(argv[optind]) >= MAXPATH) {
3285 rrd_set_error("filename (including path) too long");
3286 im_free(&im);
3287 return -1;
3288 }
3289 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3290 im.graphfile[MAXPATH - 1] = '\0';
3292 rrd_graph_script(argc, argv, &im, 1);
3293 if (rrd_test_error()) {
3294 im_free(&im);
3295 return -1;
3296 }
3298 /* Everything is now read and the actual work can start */
3300 (*prdata) = NULL;
3301 if (graph_paint(&im, prdata) == -1) {
3302 im_free(&im);
3303 return -1;
3304 }
3306 /* The image is generated and needs to be output.
3307 ** Also, if needed, print a line with information about the image.
3308 */
3310 *xsize = im.ximg;
3311 *ysize = im.yimg;
3312 *ymin = im.minval;
3313 *ymax = im.maxval;
3314 if (im.imginfo) {
3315 char *filename;
3317 if (!(*prdata)) {
3318 /* maybe prdata is not allocated yet ... lets do it now */
3319 if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3320 rrd_set_error("malloc imginfo");
3321 return -1;
3322 };
3323 }
3324 if (((*prdata)[0] =
3325 malloc((strlen(im.imginfo) + 200 +
3326 strlen(im.graphfile)) * sizeof(char)))
3327 == NULL) {
3328 rrd_set_error("malloc imginfo");
3329 return -1;
3330 }
3331 filename = im.graphfile + strlen(im.graphfile);
3332 while (filename > im.graphfile) {
3333 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3334 break;
3335 filename--;
3336 }
3338 sprintf((*prdata)[0], im.imginfo, filename,
3339 (long) (im.canvas->zoom * im.ximg),
3340 (long) (im.canvas->zoom * im.yimg));
3341 }
3342 im_free(&im);
3343 return 0;
3344 }
3346 void rrd_graph_init(
3347 image_desc_t * im)
3348 {
3349 unsigned int i;
3351 #ifdef HAVE_TZSET
3352 tzset();
3353 #endif
3354 #ifdef HAVE_SETLOCALE
3355 setlocale(LC_TIME, "");
3356 #ifdef HAVE_MBSTOWCS
3357 setlocale(LC_CTYPE, "");
3358 #endif
3359 #endif
3360 im->yorigin = 0;
3361 im->xorigin = 0;
3362 im->minval = 0;
3363 im->xlab_user.minsec = -1;
3364 im->ximg = 0;
3365 im->yimg = 0;
3366 im->xsize = 400;
3367 im->ysize = 100;
3368 im->step = 0;
3369 im->ylegend[0] = '\0';
3370 im->title[0] = '\0';
3371 im->watermark[0] = '\0';
3372 im->minval = DNAN;
3373 im->maxval = DNAN;
3374 im->unitsexponent = 9999;
3375 im->unitslength = 6;
3376 im->forceleftspace = 0;
3377 im->symbol = ' ';
3378 im->viewfactor = 1.0;
3379 im->extra_flags = 0;
3380 im->rigid = 0;
3381 im->gridfit = 1;
3382 im->imginfo = NULL;
3383 im->lazy = 0;
3384 im->slopemode = 0;
3385 im->logarithmic = 0;
3386 im->ygridstep = DNAN;
3387 im->draw_x_grid = 1;
3388 im->draw_y_grid = 1;
3389 im->base = 1000;
3390 im->prt_c = 0;
3391 im->gdes_c = 0;
3392 im->gdes = NULL;
3393 im->canvas = gfx_new_canvas();
3394 im->grid_dash_on = 1;
3395 im->grid_dash_off = 1;
3396 im->tabwidth = 40.0;
3398 for (i = 0; i < DIM(graph_col); i++)
3399 im->graph_col[i] = graph_col[i];
3401 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3402 {
3403 char *windir;
3404 char rrd_win_default_font[1000];
3406 windir = getenv("windir");
3407 /* %windir% is something like D:\windows or C:\winnt */
3408 if (windir != NULL) {
3409 strncpy(rrd_win_default_font, windir, 500);
3410 rrd_win_default_font[500] = '\0';
3411 strcat(rrd_win_default_font, "\\fonts\\");
3412 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3413 for (i = 0; i < DIM(text_prop); i++) {
3414 strncpy(text_prop[i].font, rrd_win_default_font,
3415 sizeof(text_prop[i].font) - 1);
3416 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3417 }
3418 }
3419 }
3420 #endif
3421 {
3422 char *deffont;
3424 deffont = getenv("RRD_DEFAULT_FONT");
3425 if (deffont != NULL) {
3426 for (i = 0; i < DIM(text_prop); i++) {
3427 strncpy(text_prop[i].font, deffont,
3428 sizeof(text_prop[i].font) - 1);
3429 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3430 }
3431 }
3432 }
3433 for (i = 0; i < DIM(text_prop); i++) {
3434 im->text_prop[i].size = text_prop[i].size;
3435 strcpy(im->text_prop[i].font, text_prop[i].font);
3436 }
3437 }
3439 void rrd_graph_options(
3440 int argc,
3441 char *argv[],
3442 image_desc_t * im)
3443 {
3444 int stroff;
3445 char *parsetime_error = NULL;
3446 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3447 time_t start_tmp = 0, end_tmp = 0;
3448 long long_tmp;
3449 struct rrd_time_value start_tv, end_tv;
3450 gfx_color_t color;
3452 optind = 0;
3453 opterr = 0; /* initialize getopt */
3455 parsetime("end-24h", &start_tv);
3456 parsetime("now", &end_tv);
3458 /* defines for long options without a short equivalent. should be bytes,
3459 and may not collide with (the ASCII value of) short options */
3460 #define LONGOPT_UNITS_SI 255
3462 while (1) {
3463 static struct option long_options[] = {
3464 {"start", required_argument, 0, 's'},
3465 {"end", required_argument, 0, 'e'},
3466 {"x-grid", required_argument, 0, 'x'},
3467 {"y-grid", required_argument, 0, 'y'},
3468 {"vertical-label", required_argument, 0, 'v'},
3469 {"width", required_argument, 0, 'w'},
3470 {"height", required_argument, 0, 'h'},
3471 {"interlaced", no_argument, 0, 'i'},
3472 {"upper-limit", required_argument, 0, 'u'},
3473 {"lower-limit", required_argument, 0, 'l'},
3474 {"rigid", no_argument, 0, 'r'},
3475 {"base", required_argument, 0, 'b'},
3476 {"logarithmic", no_argument, 0, 'o'},
3477 {"color", required_argument, 0, 'c'},
3478 {"font", required_argument, 0, 'n'},
3479 {"title", required_argument, 0, 't'},
3480 {"imginfo", required_argument, 0, 'f'},
3481 {"imgformat", required_argument, 0, 'a'},
3482 {"lazy", no_argument, 0, 'z'},
3483 {"zoom", required_argument, 0, 'm'},
3484 {"no-legend", no_argument, 0, 'g'},
3485 {"force-rules-legend", no_argument, 0, 'F'},
3486 {"only-graph", no_argument, 0, 'j'},
3487 {"alt-y-grid", no_argument, 0, 'Y'},
3488 {"no-minor", no_argument, 0, 'I'},
3489 {"slope-mode", no_argument, 0, 'E'},
3490 {"alt-autoscale", no_argument, 0, 'A'},
3491 {"alt-autoscale-min", no_argument, 0, 'J'},
3492 {"alt-autoscale-max", no_argument, 0, 'M'},
3493 {"no-gridfit", no_argument, 0, 'N'},
3494 {"units-exponent", required_argument, 0, 'X'},
3495 {"units-length", required_argument, 0, 'L'},
3496 {"units", required_argument, 0, LONGOPT_UNITS_SI},
3497 {"step", required_argument, 0, 'S'},
3498 {"tabwidth", required_argument, 0, 'T'},
3499 {"font-render-mode", required_argument, 0, 'R'},
3500 {"font-smoothing-threshold", required_argument, 0, 'B'},
3501 {"watermark", required_argument, 0, 'W'},
3502 {"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 */
3503 {0, 0, 0, 0}
3504 };
3505 int option_index = 0;
3506 int opt;
3507 int col_start, col_end;
3509 opt = getopt_long(argc, argv,
3510 "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3511 long_options, &option_index);
3513 if (opt == EOF)
3514 break;
3516 switch (opt) {
3517 case 'I':
3518 im->extra_flags |= NOMINOR;
3519 break;
3520 case 'Y':
3521 im->extra_flags |= ALTYGRID;
3522 break;
3523 case 'A':
3524 im->extra_flags |= ALTAUTOSCALE;
3525 break;
3526 case 'J':
3527 im->extra_flags |= ALTAUTOSCALE_MIN;
3528 break;
3529 case 'M':
3530 im->extra_flags |= ALTAUTOSCALE_MAX;
3531 break;
3532 case 'j':
3533 im->extra_flags |= ONLY_GRAPH;
3534 break;
3535 case 'g':
3536 im->extra_flags |= NOLEGEND;
3537 break;
3538 case 'F':
3539 im->extra_flags |= FORCE_RULES_LEGEND;
3540 break;
3541 case LONGOPT_UNITS_SI:
3542 if (im->extra_flags & FORCE_UNITS) {
3543 rrd_set_error("--units can only be used once!");
3544 return;
3545 }
3546 if (strcmp(optarg, "si") == 0)
3547 im->extra_flags |= FORCE_UNITS_SI;
3548 else {
3549 rrd_set_error("invalid argument for --units: %s", optarg);
3550 return;
3551 }
3552 break;
3553 case 'X':
3554 im->unitsexponent = atoi(optarg);
3555 break;
3556 case 'L':
3557 im->unitslength = atoi(optarg);
3558 im->forceleftspace = 1;
3559 break;
3560 case 'T':
3561 im->tabwidth = atof(optarg);
3562 break;
3563 case 'S':
3564 im->step = atoi(optarg);
3565 break;
3566 case 'N':
3567 im->gridfit = 0;
3568 break;
3569 case 's':
3570 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3571 rrd_set_error("start time: %s", parsetime_error);
3572 return;
3573 }
3574 break;
3575 case 'e':
3576 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3577 rrd_set_error("end time: %s", parsetime_error);
3578 return;
3579 }
3580 break;
3581 case 'x':
3582 if (strcmp(optarg, "none") == 0) {
3583 im->draw_x_grid = 0;
3584 break;
3585 };
3587 if (sscanf(optarg,
3588 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3589 scan_gtm,
3590 &im->xlab_user.gridst,
3591 scan_mtm,
3592 &im->xlab_user.mgridst,
3593 scan_ltm,
3594 &im->xlab_user.labst,
3595 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3596 strncpy(im->xlab_form, optarg + stroff,
3597 sizeof(im->xlab_form) - 1);
3598 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3599 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3600 rrd_set_error("unknown keyword %s", scan_gtm);
3601 return;
3602 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3603 == -1) {
3604 rrd_set_error("unknown keyword %s", scan_mtm);
3605 return;
3606 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3607 -1) {
3608 rrd_set_error("unknown keyword %s", scan_ltm);
3609 return;
3610 }
3611 im->xlab_user.minsec = 1;
3612 im->xlab_user.stst = im->xlab_form;
3613 } else {
3614 rrd_set_error("invalid x-grid format");
3615 return;
3616 }
3617 break;
3618 case 'y':
3620 if (strcmp(optarg, "none") == 0) {
3621 im->draw_y_grid = 0;
3622 break;
3623 };
3625 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3626 if (im->ygridstep <= 0) {
3627 rrd_set_error("grid step must be > 0");
3628 return;
3629 } else if (im->ylabfact < 1) {
3630 rrd_set_error("label factor must be > 0");
3631 return;
3632 }
3633 } else {
3634 rrd_set_error("invalid y-grid format");
3635 return;
3636 }
3637 break;
3638 case 'v':
3639 strncpy(im->ylegend, optarg, 150);
3640 im->ylegend[150] = '\0';
3641 break;
3642 case 'u':
3643 im->maxval = atof(optarg);
3644 break;
3645 case 'l':
3646 im->minval = atof(optarg);
3647 break;
3648 case 'b':
3649 im->base = atol(optarg);
3650 if (im->base != 1024 && im->base != 1000) {
3651 rrd_set_error
3652 ("the only sensible value for base apart from 1000 is 1024");
3653 return;
3654 }
3655 break;
3656 case 'w':
3657 long_tmp = atol(optarg);
3658 if (long_tmp < 10) {
3659 rrd_set_error("width below 10 pixels");
3660 return;
3661 }
3662 im->xsize = long_tmp;
3663 break;
3664 case 'h':
3665 long_tmp = atol(optarg);
3666 if (long_tmp < 10) {
3667 rrd_set_error("height below 10 pixels");
3668 return;
3669 }
3670 im->ysize = long_tmp;
3671 break;
3672 case 'i':
3673 im->canvas->interlaced = 1;
3674 break;
3675 case 'r':
3676 im->rigid = 1;
3677 break;
3678 case 'f':
3679 im->imginfo = optarg;
3680 break;
3681 case 'a':
3682 if ((int) (im->canvas->imgformat = if_conv(optarg)) == -1) {
3683 rrd_set_error("unsupported graphics format '%s'", optarg);
3684 return;
3685 }
3686 break;
3687 case 'z':
3688 im->lazy = 1;
3689 break;
3690 case 'E':
3691 im->slopemode = 1;
3692 break;
3694 case 'o':
3695 im->logarithmic = 1;
3696 break;
3697 case 'c':
3698 if (sscanf(optarg,
3699 "%10[A-Z]#%n%8lx%n",
3700 col_nam, &col_start, &color, &col_end) == 2) {
3701 int ci;
3702 int col_len = col_end - col_start;
3704 switch (col_len) {
3705 case 3:
3706 color = (((color & 0xF00) * 0x110000) |
3707 ((color & 0x0F0) * 0x011000) |
3708 ((color & 0x00F) * 0x001100) | 0x000000FF);
3709 break;
3710 case 4:
3711 color = (((color & 0xF000) * 0x11000) |
3712 ((color & 0x0F00) * 0x01100) |
3713 ((color & 0x00F0) * 0x00110) |
3714 ((color & 0x000F) * 0x00011)
3715 );
3716 break;
3717 case 6:
3718 color = (color << 8) + 0xff /* shift left by 8 */ ;
3719 break;
3720 case 8:
3721 break;
3722 default:
3723 rrd_set_error("the color format is #RRGGBB[AA]");
3724 return;
3725 }
3726 if ((ci = grc_conv(col_nam)) != -1) {
3727 im->graph_col[ci] = color;
3728 } else {
3729 rrd_set_error("invalid color name '%s'", col_nam);
3730 return;
3731 }
3732 } else {
3733 rrd_set_error("invalid color def format");
3734 return;
3735 }
3736 break;
3737 case 'n':{
3738 char prop[15];
3739 double size = 1;
3740 char font[1024] = "";
3742 if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
3743 int sindex, propidx;
3745 if ((sindex = text_prop_conv(prop)) != -1) {
3746 for (propidx = sindex; propidx < TEXT_PROP_LAST;
3747 propidx++) {
3748 if (size > 0) {
3749 im->text_prop[propidx].size = size;
3750 }
3751 if (strlen(font) > 0) {
3752 strcpy(im->text_prop[propidx].font, font);
3753 }
3754 if (propidx == sindex && sindex != 0)
3755 break;
3756 }
3757 } else {
3758 rrd_set_error("invalid fonttag '%s'", prop);
3759 return;
3760 }
3761 } else {
3762 rrd_set_error("invalid text property format");
3763 return;
3764 }
3765 break;
3766 }
3767 case 'm':
3768 im->canvas->zoom = atof(optarg);
3769 if (im->canvas->zoom <= 0.0) {
3770 rrd_set_error("zoom factor must be > 0");
3771 return;
3772 }
3773 break;
3774 case 't':
3775 strncpy(im->title, optarg, 150);
3776 im->title[150] = '\0';
3777 break;
3779 case 'R':
3780 if (strcmp(optarg, "normal") == 0)
3781 im->canvas->aa_type = AA_NORMAL;
3782 else if (strcmp(optarg, "light") == 0)
3783 im->canvas->aa_type = AA_LIGHT;
3784 else if (strcmp(optarg, "mono") == 0)
3785 im->canvas->aa_type = AA_NONE;
3786 else {
3787 rrd_set_error("unknown font-render-mode '%s'", optarg);
3788 return;
3789 }
3790 break;
3792 case 'B':
3793 im->canvas->font_aa_threshold = atof(optarg);
3794 break;
3796 case 'W':
3797 strncpy(im->watermark, optarg, 100);
3798 im->watermark[99] = '\0';
3799 break;
3801 case '?':
3802 if (optopt != 0)
3803 rrd_set_error("unknown option '%c'", optopt);
3804 else
3805 rrd_set_error("unknown option '%s'", argv[optind - 1]);
3806 return;
3807 }
3808 }
3810 if (optind >= argc) {
3811 rrd_set_error("missing filename");
3812 return;
3813 }
3815 if (im->logarithmic == 1 && im->minval <= 0) {
3816 rrd_set_error
3817 ("for a logarithmic yaxis you must specify a lower-limit > 0");
3818 return;
3819 }
3821 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
3822 /* error string is set in parsetime.c */
3823 return;
3824 }
3826 if (start_tmp < 3600 * 24 * 365 * 10) {
3827 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
3828 start_tmp);
3829 return;
3830 }
3832 if (end_tmp < start_tmp) {
3833 rrd_set_error("start (%ld) should be less than end (%ld)",
3834 start_tmp, end_tmp);
3835 return;
3836 }
3838 im->start = start_tmp;
3839 im->end = end_tmp;
3840 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
3841 }
3843 int rrd_graph_color(
3844 image_desc_t * im,
3845 char *var,
3846 char *err,
3847 int optional)
3848 {
3849 char *color;
3850 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
3852 color = strstr(var, "#");
3853 if (color == NULL) {
3854 if (optional == 0) {
3855 rrd_set_error("Found no color in %s", err);
3856 return 0;
3857 }
3858 return 0;
3859 } else {
3860 int n = 0;
3861 char *rest;
3862 gfx_color_t col;
3864 rest = strstr(color, ":");
3865 if (rest != NULL)
3866 n = rest - color;
3867 else
3868 n = strlen(color);
3870 switch (n) {
3871 case 7:
3872 sscanf(color, "#%6lx%n", &col, &n);
3873 col = (col << 8) + 0xff /* shift left by 8 */ ;
3874 if (n != 7)
3875 rrd_set_error("Color problem in %s", err);
3876 break;
3877 case 9:
3878 sscanf(color, "#%8lx%n", &col, &n);
3879 if (n == 9)
3880 break;
3881 default:
3882 rrd_set_error("Color problem in %s", err);
3883 }
3884 if (rrd_test_error())
3885 return 0;
3886 gdp->col = col;
3887 return n;
3888 }
3889 }
3892 int bad_format(
3893 char *fmt)
3894 {
3895 char *ptr;
3896 int n = 0;
3898 ptr = fmt;
3899 while (*ptr != '\0')
3900 if (*ptr++ == '%') {
3902 /* line cannot end with percent char */
3903 if (*ptr == '\0')
3904 return 1;
3906 /* '%s', '%S' and '%%' are allowed */
3907 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
3908 ptr++;
3910 /* %c is allowed (but use only with vdef!) */
3911 else if (*ptr == 'c') {
3912 ptr++;
3913 n = 1;
3914 }
3916 /* or else '% 6.2lf' and such are allowed */
3917 else {
3918 /* optional padding character */
3919 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
3920 ptr++;
3922 /* This should take care of 'm.n' with all three optional */
3923 while (*ptr >= '0' && *ptr <= '9')
3924 ptr++;
3925 if (*ptr == '.')
3926 ptr++;
3927 while (*ptr >= '0' && *ptr <= '9')
3928 ptr++;
3930 /* Either 'le', 'lf' or 'lg' must follow here */
3931 if (*ptr++ != 'l')
3932 return 1;
3933 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
3934 ptr++;
3935 else
3936 return 1;
3937 n++;
3938 }
3939 }
3941 return (n != 1);
3942 }
3945 int vdef_parse(
3946 gdes,
3947 str)
3948 struct graph_desc_t *gdes;
3949 const char *const str;
3950 {
3951 /* A VDEF currently is either "func" or "param,func"
3952 * so the parsing is rather simple. Change if needed.
3953 */
3954 double param;
3955 char func[30];
3956 int n;
3958 n = 0;
3959 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
3960 if (n == (int) strlen(str)) { /* matched */
3961 ;
3962 } else {
3963 n = 0;
3964 sscanf(str, "%29[A-Z]%n", func, &n);
3965 if (n == (int) strlen(str)) { /* matched */
3966 param = DNAN;
3967 } else {
3968 rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
3969 gdes->vname);
3970 return -1;
3971 }
3972 }
3973 if (!strcmp("PERCENT", func))
3974 gdes->vf.op = VDEF_PERCENT;
3975 else if (!strcmp("MAXIMUM", func))
3976 gdes->vf.op = VDEF_MAXIMUM;
3977 else if (!strcmp("AVERAGE", func))
3978 gdes->vf.op = VDEF_AVERAGE;
3979 else if (!strcmp("MINIMUM", func))
3980 gdes->vf.op = VDEF_MINIMUM;
3981 else if (!strcmp("TOTAL", func))
3982 gdes->vf.op = VDEF_TOTAL;
3983 else if (!strcmp("FIRST", func))
3984 gdes->vf.op = VDEF_FIRST;
3985 else if (!strcmp("LAST", func))
3986 gdes->vf.op = VDEF_LAST;
3987 else if (!strcmp("LSLSLOPE", func))
3988 gdes->vf.op = VDEF_LSLSLOPE;
3989 else if (!strcmp("LSLINT", func))
3990 gdes->vf.op = VDEF_LSLINT;
3991 else if (!strcmp("LSLCORREL", func))
3992 gdes->vf.op = VDEF_LSLCORREL;
3993 else {
3994 rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
3995 gdes->vname);
3996 return -1;
3997 };
3999 switch (gdes->vf.op) {
4000 case VDEF_PERCENT:
4001 if (isnan(param)) { /* no parameter given */
4002 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4003 func, gdes->vname);
4004 return -1;
4005 };
4006 if (param >= 0.0 && param <= 100.0) {
4007 gdes->vf.param = param;
4008 gdes->vf.val = DNAN; /* undefined */
4009 gdes->vf.when = 0; /* undefined */
4010 } else {
4011 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4012 gdes->vname);
4013 return -1;
4014 };
4015 break;
4016 case VDEF_MAXIMUM:
4017 case VDEF_AVERAGE:
4018 case VDEF_MINIMUM:
4019 case VDEF_TOTAL:
4020 case VDEF_FIRST:
4021 case VDEF_LAST:
4022 case VDEF_LSLSLOPE:
4023 case VDEF_LSLINT:
4024 case VDEF_LSLCORREL:
4025 if (isnan(param)) {
4026 gdes->vf.param = DNAN;
4027 gdes->vf.val = DNAN;
4028 gdes->vf.when = 0;
4029 } else {
4030 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4031 func, gdes->vname);
4032 return -1;
4033 };
4034 break;
4035 };
4036 return 0;
4037 }
4040 int vdef_calc(
4041 im,
4042 gdi)
4043 image_desc_t *im;
4044 int gdi;
4045 {
4046 graph_desc_t *src, *dst;
4047 rrd_value_t *data;
4048 long step, steps;
4050 dst = &im->gdes[gdi];
4051 src = &im->gdes[dst->vidx];
4052 data = src->data + src->ds;
4053 steps = (src->end - src->start) / src->step;
4055 #if 0
4056 printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4057 src->end, steps);
4058 #endif
4060 switch (dst->vf.op) {
4061 case VDEF_PERCENT:{
4062 rrd_value_t *array;
4063 int field;
4066 if ((array = malloc(steps * sizeof(double))) == NULL) {
4067 rrd_set_error("malloc VDEV_PERCENT");
4068 return -1;
4069 }
4070 for (step = 0; step < steps; step++) {
4071 array[step] = data[step * src->ds_cnt];
4072 }
4073 qsort(array, step, sizeof(double), vdef_percent_compar);
4075 field = (steps - 1) * dst->vf.param / 100;
4076 dst->vf.val = array[field];
4077 dst->vf.when = 0; /* no time component */
4078 free(array);
4079 #if 0
4080 for (step = 0; step < steps; step++)
4081 printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4082 step == field ? '*' : ' ');
4083 #endif
4084 }
4085 break;
4086 case VDEF_MAXIMUM:
4087 step = 0;
4088 while (step != steps && isnan(data[step * src->ds_cnt]))
4089 step++;
4090 if (step == steps) {
4091 dst->vf.val = DNAN;
4092 dst->vf.when = 0;
4093 } else {
4094 dst->vf.val = data[step * src->ds_cnt];
4095 dst->vf.when = src->start + (step + 1) * src->step;
4096 }
4097 while (step != steps) {
4098 if (finite(data[step * src->ds_cnt])) {
4099 if (data[step * src->ds_cnt] > dst->vf.val) {
4100 dst->vf.val = data[step * src->ds_cnt];
4101 dst->vf.when = src->start + (step + 1) * src->step;
4102 }
4103 }
4104 step++;
4105 }
4106 break;
4107 case VDEF_TOTAL:
4108 case VDEF_AVERAGE:{
4109 int cnt = 0;
4110 double sum = 0.0;
4112 for (step = 0; step < steps; step++) {
4113 if (finite(data[step * src->ds_cnt])) {
4114 sum += data[step * src->ds_cnt];
4115 cnt++;
4116 };
4117 }
4118 if (cnt) {
4119 if (dst->vf.op == VDEF_TOTAL) {
4120 dst->vf.val = sum * src->step;
4121 dst->vf.when = 0; /* no time component */
4122 } else {
4123 dst->vf.val = sum / cnt;
4124 dst->vf.when = 0; /* no time component */
4125 };
4126 } else {
4127 dst->vf.val = DNAN;
4128 dst->vf.when = 0;
4129 }
4130 }
4131 break;
4132 case VDEF_MINIMUM:
4133 step = 0;
4134 while (step != steps && isnan(data[step * src->ds_cnt]))
4135 step++;
4136 if (step == steps) {
4137 dst->vf.val = DNAN;
4138 dst->vf.when = 0;
4139 } else {
4140 dst->vf.val = data[step * src->ds_cnt];
4141 dst->vf.when = src->start + (step + 1) * src->step;
4142 }
4143 while (step != steps) {
4144 if (finite(data[step * src->ds_cnt])) {
4145 if (data[step * src->ds_cnt] < dst->vf.val) {
4146 dst->vf.val = data[step * src->ds_cnt];
4147 dst->vf.when = src->start + (step + 1) * src->step;
4148 }
4149 }
4150 step++;
4151 }
4152 break;
4153 case VDEF_FIRST:
4154 /* The time value returned here is one step before the
4155 * actual time value. This is the start of the first
4156 * non-NaN interval.
4157 */
4158 step = 0;
4159 while (step != steps && isnan(data[step * src->ds_cnt]))
4160 step++;
4161 if (step == steps) { /* all entries were NaN */
4162 dst->vf.val = DNAN;
4163 dst->vf.when = 0;
4164 } else {
4165 dst->vf.val = data[step * src->ds_cnt];
4166 dst->vf.when = src->start + step * src->step;
4167 }
4168 break;
4169 case VDEF_LAST:
4170 /* The time value returned here is the
4171 * actual time value. This is the end of the last
4172 * non-NaN interval.
4173 */
4174 step = steps - 1;
4175 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4176 step--;
4177 if (step < 0) { /* all entries were NaN */
4178 dst->vf.val = DNAN;
4179 dst->vf.when = 0;
4180 } else {
4181 dst->vf.val = data[step * src->ds_cnt];
4182 dst->vf.when = src->start + (step + 1) * src->step;
4183 }
4184 break;
4185 case VDEF_LSLSLOPE:
4186 case VDEF_LSLINT:
4187 case VDEF_LSLCORREL:{
4188 /* Bestfit line by linear least squares method */
4190 int cnt = 0;
4191 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4193 SUMx = 0;
4194 SUMy = 0;
4195 SUMxy = 0;
4196 SUMxx = 0;
4197 SUMyy = 0;
4199 for (step = 0; step < steps; step++) {
4200 if (finite(data[step * src->ds_cnt])) {
4201 cnt++;
4202 SUMx += step;
4203 SUMxx += step * step;
4204 SUMxy += step * data[step * src->ds_cnt];
4205 SUMy += data[step * src->ds_cnt];
4206 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4207 };
4208 }
4210 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4211 y_intercept = (SUMy - slope * SUMx) / cnt;
4212 correl =
4213 (SUMxy -
4214 (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4215 (SUMx * SUMx) / cnt) * (SUMyy -
4216 (SUMy *
4217 SUMy) /
4218 cnt));
4220 if (cnt) {
4221 if (dst->vf.op == VDEF_LSLSLOPE) {
4222 dst->vf.val = slope;
4223 dst->vf.when = 0;
4224 } else if (dst->vf.op == VDEF_LSLINT) {
4225 dst->vf.val = y_intercept;
4226 dst->vf.when = 0;
4227 } else if (dst->vf.op == VDEF_LSLCORREL) {
4228 dst->vf.val = correl;
4229 dst->vf.when = 0;
4230 };
4232 } else {
4233 dst->vf.val = DNAN;
4234 dst->vf.when = 0;
4235 }
4236 }
4237 break;
4238 }
4239 return 0;
4240 }
4242 /* NaN < -INF < finite_values < INF */
4243 int vdef_percent_compar(
4244 a,
4245 b)
4246 const void *a, *b;
4247 {
4248 /* Equality is not returned; this doesn't hurt except
4249 * (maybe) for a little performance.
4250 */
4252 /* First catch NaN values. They are smallest */
4253 if (isnan(*(double *) a))
4254 return -1;
4255 if (isnan(*(double *) b))
4256 return 1;
4258 /* NaN doesn't reach this part so INF and -INF are extremes.
4259 * The sign from isinf() is compatible with the sign we return
4260 */
4261 if (isinf(*(double *) a))
4262 return isinf(*(double *) a);
4263 if (isinf(*(double *) b))
4264 return isinf(*(double *) b);
4266 /* If we reach this, both values must be finite */
4267 if (*(double *) a < *(double *) b)
4268 return -1;
4269 else
4270 return 1;
4271 }