1 /****************************************************************************
2 * RRDtool 1.2.99907080300 Copyright by Tobi Oetiker, 1997-2007
3 ****************************************************************************
4 * rrd__graph.c produce graphs from data in rrdfiles
5 ****************************************************************************/
15 #if defined(WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
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"
39 text_prop_t text_prop[] = {
40 {8.0, RRD_DEFAULT_FONT}
42 {9.0, RRD_DEFAULT_FONT}
44 {7.0, RRD_DEFAULT_FONT}
46 {8.0, RRD_DEFAULT_FONT}
48 {8.0, RRD_DEFAULT_FONT} /* legend */
52 {0, 0, TMT_SECOND, 30, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
54 {2, 0, TMT_MINUTE, 1, TMT_MINUTE, 5, TMT_MINUTE, 5, 0, "%H:%M"}
56 {5, 0, TMT_MINUTE, 2, TMT_MINUTE, 10, TMT_MINUTE, 10, 0, "%H:%M"}
58 {10, 0, TMT_MINUTE, 5, TMT_MINUTE, 20, TMT_MINUTE, 20, 0, "%H:%M"}
60 {30, 0, TMT_MINUTE, 10, TMT_HOUR, 1, TMT_HOUR, 1, 0, "%H:%M"}
62 {60, 0, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 2, 0, "%H:%M"}
64 {60, 24 * 3600, TMT_MINUTE, 30, TMT_HOUR, 2, TMT_HOUR, 4, 0, "%a %H:%M"}
66 {180, 0, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 6, 0, "%H:%M"}
68 {180, 24 * 3600, TMT_HOUR, 1, TMT_HOUR, 6, TMT_HOUR, 12, 0, "%a %H:%M"}
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"}
73 {1200, 0, TMT_HOUR, 6, TMT_DAY, 1, TMT_DAY, 1, 24 * 3600, "%d"}
75 {1800, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a %d"}
77 {2400, 0, TMT_HOUR, 12, TMT_DAY, 1, TMT_DAY, 2, 24 * 3600, "%a"}
79 {3600, 0, TMT_DAY, 1, TMT_WEEK, 1, TMT_WEEK, 1, 7 * 24 * 3600, "Week %V"}
81 {3 * 3600, 0, TMT_WEEK, 1, TMT_MONTH, 1, TMT_WEEK, 2, 7 * 24 * 3600,
84 {6 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 1, TMT_MONTH, 1, 30 * 24 * 3600,
87 {48 * 3600, 0, TMT_MONTH, 1, TMT_MONTH, 3, TMT_MONTH, 3, 30 * 24 * 3600,
90 {315360, 0, TMT_MONTH, 3, TMT_YEAR, 1, TMT_YEAR, 1, 365 * 24 * 3600, "%Y"}
92 {10 * 24 * 3600, 0, TMT_YEAR, 1, TMT_YEAR, 1, TMT_YEAR, 1,
93 365 * 24 * 3600, "%y"}
95 {-1, 0, TMT_MONTH, 0, TMT_MONTH, 0, TMT_MONTH, 0, 0, ""}
98 /* sensible y label intervals ...*/
122 {20.0, {1, 5, 10, 20}
128 {100.0, {1, 2, 5, 10}
131 {200.0, {1, 5, 10, 20}
134 {500.0, {1, 2, 4, 10}
142 gfx_color_t graph_col[] = /* default colors */
144 {1.00, 1.00, 1.00, 1.00}, /* canvas */
145 {0.95, 0.95, 0.95, 1.00}, /* background */
146 {0.81, 0.81, 0.81, 1.00}, /* shade A */
147 {0.62, 0.62, 0.62, 1.00}, /* shade B */
148 {0.56, 0.56, 0.56, 0.75}, /* grid */
149 {0.87, 0.31, 0.31, 0.60}, /* major grid */
150 {0.00, 0.00, 0.00, 1.00}, /* font */
151 {0.50, 0.12, 0.12, 1.00}, /* arrow */
152 {0.12, 0.12, 0.12, 1.00}, /* axis */
153 {0.00, 0.00, 0.00, 1.00} /* frame */
160 # define DPRINT(x) (void)(printf x, printf("\n"))
166 /* initialize with xtr(im,0); */
174 pixie = (double) im->xsize / (double) (im->end - im->start);
177 return (int) ((double) im->xorigin + pixie * (mytime - im->start));
180 /* translate data values into y coordinates */
189 if (!im->logarithmic)
190 pixie = (double) im->ysize / (im->maxval - im->minval);
193 (double) im->ysize / (log10(im->maxval) - log10(im->minval));
195 } else if (!im->logarithmic) {
196 yval = im->yorigin - pixie * (value - im->minval);
198 if (value < im->minval) {
201 yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
204 /* make sure we don't return anything too unreasonable. GD lib can
205 get terribly slow when drawing lines outside its scope. This is
206 especially problematic in connection with the rigid option */
208 /* keep yval as-is */
209 } else if (yval > im->yorigin) {
210 yval = im->yorigin + 0.00001;
211 } else if (yval < im->yorigin - im->ysize) {
212 yval = im->yorigin - im->ysize - 0.00001;
219 /* conversion function for symbolic entry names */
222 #define conv_if(VV,VVV) \
223 if (strcmp(#VV, string) == 0) return VVV ;
229 conv_if(PRINT, GF_PRINT);
230 conv_if(GPRINT, GF_GPRINT);
231 conv_if(COMMENT, GF_COMMENT);
232 conv_if(HRULE, GF_HRULE);
233 conv_if(VRULE, GF_VRULE);
234 conv_if(LINE, GF_LINE);
235 conv_if(AREA, GF_AREA);
236 conv_if(STACK, GF_STACK);
237 conv_if(TICK, GF_TICK);
238 conv_if(TEXTALIGN, GF_TEXTALIGN);
239 conv_if(DEF, GF_DEF);
240 conv_if(CDEF, GF_CDEF);
241 conv_if(VDEF, GF_VDEF);
242 conv_if(XPORT, GF_XPORT);
243 conv_if(SHIFT, GF_SHIFT);
248 enum gfx_if_en if_conv(
252 conv_if(PNG, IF_PNG);
253 conv_if(SVG, IF_SVG);
254 conv_if(EPS, IF_EPS);
255 conv_if(PDF, IF_PDF);
260 enum tmt_en tmt_conv(
264 conv_if(SECOND, TMT_SECOND);
265 conv_if(MINUTE, TMT_MINUTE);
266 conv_if(HOUR, TMT_HOUR);
267 conv_if(DAY, TMT_DAY);
268 conv_if(WEEK, TMT_WEEK);
269 conv_if(MONTH, TMT_MONTH);
270 conv_if(YEAR, TMT_YEAR);
274 enum grc_en grc_conv(
278 conv_if(BACK, GRC_BACK);
279 conv_if(CANVAS, GRC_CANVAS);
280 conv_if(SHADEA, GRC_SHADEA);
281 conv_if(SHADEB, GRC_SHADEB);
282 conv_if(GRID, GRC_GRID);
283 conv_if(MGRID, GRC_MGRID);
284 conv_if(FONT, GRC_FONT);
285 conv_if(ARROW, GRC_ARROW);
286 conv_if(AXIS, GRC_AXIS);
287 conv_if(FRAME, GRC_FRAME);
292 enum text_prop_en text_prop_conv(
296 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
297 conv_if(TITLE, TEXT_PROP_TITLE);
298 conv_if(AXIS, TEXT_PROP_AXIS);
299 conv_if(UNIT, TEXT_PROP_UNIT);
300 conv_if(LEGEND, TEXT_PROP_LEGEND);
311 cairo_status_t status = 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);
325 free(im->gdes[i].p_data);
326 free(im->gdes[i].rpnp);
329 if (im->font_options)
330 cairo_font_options_destroy(im->font_options);
333 status = cairo_status(im->cr);
334 cairo_destroy(im->cr);
337 cairo_surface_destroy(im->surface);
339 fprintf(stderr, "OOPS: Cairo has issuesm it can't even die: %s\n",
340 cairo_status_to_string(status));
345 /* find SI magnitude symbol for the given number*/
347 image_desc_t *im, /* image description */
353 char *symbol[] = { "a", /* 10e-18 Atto */
354 "f", /* 10e-15 Femto */
355 "p", /* 10e-12 Pico */
356 "n", /* 10e-9 Nano */
357 "u", /* 10e-6 Micro */
358 "m", /* 10e-3 Milli */
363 "T", /* 10e12 Tera */
364 "P", /* 10e15 Peta */
371 if (*value == 0.0 || isnan(*value)) {
375 sindex = floor(log(fabs(*value)) / log((double) im->base));
376 *magfact = pow((double) im->base, (double) sindex);
377 (*value) /= (*magfact);
379 if (sindex <= symbcenter && sindex >= -symbcenter) {
380 (*symb_ptr) = symbol[sindex + symbcenter];
387 static char si_symbol[] = {
388 'a', /* 10e-18 Atto */
389 'f', /* 10e-15 Femto */
390 'p', /* 10e-12 Pico */
391 'n', /* 10e-9 Nano */
392 'u', /* 10e-6 Micro */
393 'm', /* 10e-3 Milli */
398 'T', /* 10e12 Tera */
399 'P', /* 10e15 Peta */
402 static const int si_symbcenter = 6;
404 /* find SI magnitude symbol for the numbers on the y-axis*/
406 image_desc_t *im /* image description */
410 double digits, viewdigits = 0;
413 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
414 log((double) im->base));
416 if (im->unitsexponent != 9999) {
417 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
418 viewdigits = floor(im->unitsexponent / 3);
423 im->magfact = pow((double) im->base, digits);
426 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
429 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
431 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
432 ((viewdigits + si_symbcenter) >= 0))
433 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
438 /* move min and max values around to become sensible */
443 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
444 600.0, 500.0, 400.0, 300.0, 250.0,
445 200.0, 125.0, 100.0, 90.0, 80.0,
446 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
447 25.0, 20.0, 10.0, 9.0, 8.0,
448 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
449 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
450 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
453 double scaled_min, scaled_max;
460 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
461 im->minval, im->maxval, im->magfact);
464 if (isnan(im->ygridstep)) {
465 if (im->extra_flags & ALTAUTOSCALE) {
466 /* measure the amplitude of the function. Make sure that
467 graph boundaries are slightly higher then max/min vals
468 so we can see amplitude on the graph */
471 delt = im->maxval - im->minval;
473 fact = 2.0 * pow(10.0,
475 (max(fabs(im->minval), fabs(im->maxval)) /
478 adj = (fact - delt) * 0.55;
481 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
482 im->minval, im->maxval, delt, fact, adj);
487 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
488 /* measure the amplitude of the function. Make sure that
489 graph boundaries are slightly lower than min vals
490 so we can see amplitude on the graph */
491 adj = (im->maxval - im->minval) * 0.1;
493 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
494 /* measure the amplitude of the function. Make sure that
495 graph boundaries are slightly higher than max vals
496 so we can see amplitude on the graph */
497 adj = (im->maxval - im->minval) * 0.1;
500 scaled_min = im->minval / im->magfact;
501 scaled_max = im->maxval / im->magfact;
503 for (i = 1; sensiblevalues[i] > 0; i++) {
504 if (sensiblevalues[i - 1] >= scaled_min &&
505 sensiblevalues[i] <= scaled_min)
506 im->minval = sensiblevalues[i] * (im->magfact);
508 if (-sensiblevalues[i - 1] <= scaled_min &&
509 -sensiblevalues[i] >= scaled_min)
510 im->minval = -sensiblevalues[i - 1] * (im->magfact);
512 if (sensiblevalues[i - 1] >= scaled_max &&
513 sensiblevalues[i] <= scaled_max)
514 im->maxval = sensiblevalues[i - 1] * (im->magfact);
516 if (-sensiblevalues[i - 1] <= scaled_max &&
517 -sensiblevalues[i] >= scaled_max)
518 im->maxval = -sensiblevalues[i] * (im->magfact);
522 /* adjust min and max to the grid definition if there is one */
523 im->minval = (double) im->ylabfact * im->ygridstep *
524 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
525 im->maxval = (double) im->ylabfact * im->ygridstep *
526 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
530 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
531 im->minval, im->maxval, im->magfact);
539 if (isnan(im->minval) || isnan(im->maxval))
542 if (im->logarithmic) {
543 double ya, yb, ypix, ypixfrac;
544 double log10_range = log10(im->maxval) - log10(im->minval);
546 ya = pow((double) 10, floor(log10(im->minval)));
547 while (ya < im->minval)
550 return; /* don't have y=10^x gridline */
552 if (yb <= im->maxval) {
553 /* we have at least 2 y=10^x gridlines.
554 Make sure distance between them in pixels
555 are an integer by expanding im->maxval */
556 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
557 double factor = y_pixel_delta / floor(y_pixel_delta);
558 double new_log10_range = factor * log10_range;
559 double new_ymax_log10 = log10(im->minval) + new_log10_range;
561 im->maxval = pow(10, new_ymax_log10);
562 ytr(im, DNAN); /* reset precalc */
563 log10_range = log10(im->maxval) - log10(im->minval);
565 /* make sure first y=10^x gridline is located on
566 integer pixel position by moving scale slightly
567 downwards (sub-pixel movement) */
568 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
569 ypixfrac = ypix - floor(ypix);
570 if (ypixfrac > 0 && ypixfrac < 1) {
571 double yfrac = ypixfrac / im->ysize;
573 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
574 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
575 ytr(im, DNAN); /* reset precalc */
578 /* Make sure we have an integer pixel distance between
579 each minor gridline */
580 double ypos1 = ytr(im, im->minval);
581 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
582 double y_pixel_delta = ypos1 - ypos2;
583 double factor = y_pixel_delta / floor(y_pixel_delta);
584 double new_range = factor * (im->maxval - im->minval);
585 double gridstep = im->ygrid_scale.gridstep;
586 double minor_y, minor_y_px, minor_y_px_frac;
588 if (im->maxval > 0.0)
589 im->maxval = im->minval + new_range;
591 im->minval = im->maxval - new_range;
592 ytr(im, DNAN); /* reset precalc */
593 /* make sure first minor gridline is on integer pixel y coord */
594 minor_y = gridstep * floor(im->minval / gridstep);
595 while (minor_y < im->minval)
597 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
598 minor_y_px_frac = minor_y_px - floor(minor_y_px);
599 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
600 double yfrac = minor_y_px_frac / im->ysize;
601 double range = im->maxval - im->minval;
603 im->minval = im->minval - yfrac * range;
604 im->maxval = im->maxval - yfrac * range;
605 ytr(im, DNAN); /* reset precalc */
607 calc_horizontal_grid(im); /* recalc with changed im->maxval */
611 /* reduce data reimplementation by Alex */
614 enum cf_en cf, /* which consolidation function ? */
615 unsigned long cur_step, /* step the data currently is in */
616 time_t *start, /* start, end and step as requested ... */
617 time_t *end, /* ... by the application will be ... */
618 unsigned long *step, /* ... adjusted to represent reality */
619 unsigned long *ds_cnt, /* number of data sources in file */
621 { /* two dimensional array containing the data */
622 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
623 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
625 rrd_value_t *srcptr, *dstptr;
627 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
630 row_cnt = ((*end) - (*start)) / cur_step;
636 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
637 row_cnt, reduce_factor, *start, *end, cur_step);
638 for (col = 0; col < row_cnt; col++) {
639 printf("time %10lu: ", *start + (col + 1) * cur_step);
640 for (i = 0; i < *ds_cnt; i++)
641 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
646 /* We have to combine [reduce_factor] rows of the source
647 ** into one row for the destination. Doing this we also
648 ** need to take care to combine the correct rows. First
649 ** alter the start and end time so that they are multiples
650 ** of the new step time. We cannot reduce the amount of
651 ** time so we have to move the end towards the future and
652 ** the start towards the past.
654 end_offset = (*end) % (*step);
655 start_offset = (*start) % (*step);
657 /* If there is a start offset (which cannot be more than
658 ** one destination row), skip the appropriate number of
659 ** source rows and one destination row. The appropriate
660 ** number is what we do know (start_offset/cur_step) of
661 ** the new interval (*step/cur_step aka reduce_factor).
664 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
665 printf("row_cnt before: %lu\n", row_cnt);
668 (*start) = (*start) - start_offset;
669 skiprows = reduce_factor - start_offset / cur_step;
670 srcptr += skiprows * *ds_cnt;
671 for (col = 0; col < (*ds_cnt); col++)
676 printf("row_cnt between: %lu\n", row_cnt);
679 /* At the end we have some rows that are not going to be
680 ** used, the amount is end_offset/cur_step
683 (*end) = (*end) - end_offset + (*step);
684 skiprows = end_offset / cur_step;
688 printf("row_cnt after: %lu\n", row_cnt);
691 /* Sanity check: row_cnt should be multiple of reduce_factor */
692 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
694 if (row_cnt % reduce_factor) {
695 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
696 row_cnt, reduce_factor);
697 printf("BUG in reduce_data()\n");
701 /* Now combine reduce_factor intervals at a time
702 ** into one interval for the destination.
705 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
706 for (col = 0; col < (*ds_cnt); col++) {
707 rrd_value_t newval = DNAN;
708 unsigned long validval = 0;
710 for (i = 0; i < reduce_factor; i++) {
711 if (isnan(srcptr[i * (*ds_cnt) + col])) {
716 newval = srcptr[i * (*ds_cnt) + col];
725 newval += srcptr[i * (*ds_cnt) + col];
728 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
731 /* an interval contains a failure if any subintervals contained a failure */
733 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
736 newval = srcptr[i * (*ds_cnt) + col];
762 srcptr += (*ds_cnt) * reduce_factor;
763 row_cnt -= reduce_factor;
765 /* If we had to alter the endtime, we didn't have enough
766 ** source rows to fill the last row. Fill it with NaN.
769 for (col = 0; col < (*ds_cnt); col++)
772 row_cnt = ((*end) - (*start)) / *step;
774 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
775 row_cnt, *start, *end, *step);
776 for (col = 0; col < row_cnt; col++) {
777 printf("time %10lu: ", *start + (col + 1) * (*step));
778 for (i = 0; i < *ds_cnt; i++)
779 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
786 /* get the data required for the graphs from the
795 /* pull the data from the rrd files ... */
796 for (i = 0; i < (int) im->gdes_c; i++) {
797 /* only GF_DEF elements fetch data */
798 if (im->gdes[i].gf != GF_DEF)
802 /* do we have it already ? */
803 for (ii = 0; ii < i; ii++) {
804 if (im->gdes[ii].gf != GF_DEF)
806 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
807 && (im->gdes[i].cf == im->gdes[ii].cf)
808 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
809 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
810 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
811 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
812 /* OK, the data is already there.
813 ** Just copy the header portion
815 im->gdes[i].start = im->gdes[ii].start;
816 im->gdes[i].end = im->gdes[ii].end;
817 im->gdes[i].step = im->gdes[ii].step;
818 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
819 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
820 im->gdes[i].data = im->gdes[ii].data;
821 im->gdes[i].data_first = 0;
828 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
830 if ((rrd_fetch_fn(im->gdes[i].rrd,
836 &im->gdes[i].ds_namv,
837 &im->gdes[i].data)) == -1) {
840 im->gdes[i].data_first = 1;
842 if (ft_step < im->gdes[i].step) {
843 reduce_data(im->gdes[i].cf_reduce,
848 &im->gdes[i].ds_cnt, &im->gdes[i].data);
850 im->gdes[i].step = ft_step;
854 /* lets see if the required data source is really there */
855 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
856 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
860 if (im->gdes[i].ds == -1) {
861 rrd_set_error("No DS called '%s' in '%s'",
862 im->gdes[i].ds_nam, im->gdes[i].rrd);
870 /* evaluate the expressions in the CDEF functions */
872 /*************************************************************
874 *************************************************************/
876 long find_var_wrapper(
880 return find_var((image_desc_t *) arg1, key);
883 /* find gdes containing var*/
890 for (ii = 0; ii < im->gdes_c - 1; ii++) {
891 if ((im->gdes[ii].gf == GF_DEF
892 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
893 && (strcmp(im->gdes[ii].vname, key) == 0)) {
900 /* find the largest common denominator for all the numbers
901 in the 0 terminated num array */
908 for (i = 0; num[i + 1] != 0; i++) {
910 rest = num[i] % num[i + 1];
916 /* return i==0?num[i]:num[i-1]; */
920 /* run the rpn calculator on all the VDEF and CDEF arguments */
927 long *steparray, rpi;
932 rpnstack_init(&rpnstack);
934 for (gdi = 0; gdi < im->gdes_c; gdi++) {
935 /* Look for GF_VDEF and GF_CDEF in the same loop,
936 * so CDEFs can use VDEFs and vice versa
938 switch (im->gdes[gdi].gf) {
942 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
944 /* remove current shift */
945 vdp->start -= vdp->shift;
946 vdp->end -= vdp->shift;
949 if (im->gdes[gdi].shidx >= 0)
950 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
953 vdp->shift = im->gdes[gdi].shval;
955 /* normalize shift to multiple of consolidated step */
956 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
959 vdp->start += vdp->shift;
960 vdp->end += vdp->shift;
964 /* A VDEF has no DS. This also signals other parts
965 * of rrdtool that this is a VDEF value, not a CDEF.
967 im->gdes[gdi].ds_cnt = 0;
968 if (vdef_calc(im, gdi)) {
969 rrd_set_error("Error processing VDEF '%s'",
970 im->gdes[gdi].vname);
971 rpnstack_free(&rpnstack);
976 im->gdes[gdi].ds_cnt = 1;
977 im->gdes[gdi].ds = 0;
978 im->gdes[gdi].data_first = 1;
979 im->gdes[gdi].start = 0;
980 im->gdes[gdi].end = 0;
985 /* Find the variables in the expression.
986 * - VDEF variables are substituted by their values
987 * and the opcode is changed into OP_NUMBER.
988 * - CDEF variables are analized for their step size,
989 * the lowest common denominator of all the step
990 * sizes of the data sources involved is calculated
991 * and the resulting number is the step size for the
992 * resulting data source.
994 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
995 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
996 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
997 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
999 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1002 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1003 im->gdes[gdi].vname, im->gdes[ptr].vname);
1004 printf("DEBUG: value from vdef is %f\n",
1005 im->gdes[ptr].vf.val);
1007 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1008 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1009 } else { /* normal variables and PREF(variables) */
1011 /* add one entry to the array that keeps track of the step sizes of the
1012 * data sources going into the CDEF. */
1014 rrd_realloc(steparray,
1016 1) * sizeof(*steparray))) == NULL) {
1017 rrd_set_error("realloc steparray");
1018 rpnstack_free(&rpnstack);
1022 steparray[stepcnt - 1] = im->gdes[ptr].step;
1024 /* adjust start and end of cdef (gdi) so
1025 * that it runs from the latest start point
1026 * to the earliest endpoint of any of the
1027 * rras involved (ptr)
1030 if (im->gdes[gdi].start < im->gdes[ptr].start)
1031 im->gdes[gdi].start = im->gdes[ptr].start;
1033 if (im->gdes[gdi].end == 0 ||
1034 im->gdes[gdi].end > im->gdes[ptr].end)
1035 im->gdes[gdi].end = im->gdes[ptr].end;
1037 /* store pointer to the first element of
1038 * the rra providing data for variable,
1039 * further save step size and data source
1042 im->gdes[gdi].rpnp[rpi].data =
1043 im->gdes[ptr].data + im->gdes[ptr].ds;
1044 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1045 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1047 /* backoff the *.data ptr; this is done so
1048 * rpncalc() function doesn't have to treat
1049 * the first case differently
1051 } /* if ds_cnt != 0 */
1052 } /* if OP_VARIABLE */
1053 } /* loop through all rpi */
1055 /* move the data pointers to the correct period */
1056 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1057 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1058 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1059 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1061 im->gdes[gdi].start - im->gdes[ptr].start;
1064 im->gdes[gdi].rpnp[rpi].data +=
1065 (diff / im->gdes[ptr].step) *
1066 im->gdes[ptr].ds_cnt;
1070 if (steparray == NULL) {
1071 rrd_set_error("rpn expressions without DEF"
1072 " or CDEF variables are not supported");
1073 rpnstack_free(&rpnstack);
1076 steparray[stepcnt] = 0;
1077 /* Now find the resulting step. All steps in all
1078 * used RRAs have to be visited
1080 im->gdes[gdi].step = lcd(steparray);
1082 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1083 im->gdes[gdi].start)
1084 / im->gdes[gdi].step)
1085 * sizeof(double))) == NULL) {
1086 rrd_set_error("malloc im->gdes[gdi].data");
1087 rpnstack_free(&rpnstack);
1091 /* Step through the new cdef results array and
1092 * calculate the values
1094 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1095 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1096 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1098 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1099 * in this case we are advancing by timesteps;
1100 * we use the fact that time_t is a synonym for long
1102 if (rpn_calc(rpnp, &rpnstack, (long) now,
1103 im->gdes[gdi].data, ++dataidx) == -1) {
1104 /* rpn_calc sets the error string */
1105 rpnstack_free(&rpnstack);
1108 } /* enumerate over time steps within a CDEF */
1113 } /* enumerate over CDEFs */
1114 rpnstack_free(&rpnstack);
1118 static int AlmostEqual2sComplement(
1124 int aInt = *(int *) &A;
1125 int bInt = *(int *) &B;
1128 /* Make sure maxUlps is non-negative and small enough that the
1129 default NAN won't compare as equal to anything. */
1131 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1133 /* Make aInt lexicographically ordered as a twos-complement int */
1136 aInt = 0x80000000l - aInt;
1138 /* Make bInt lexicographically ordered as a twos-complement int */
1141 bInt = 0x80000000l - bInt;
1143 intDiff = abs(aInt - bInt);
1145 if (intDiff <= maxUlps)
1151 /* massage data so, that we get one value for each x coordinate in the graph */
1156 double pixstep = (double) (im->end - im->start)
1157 / (double) im->xsize; /* how much time
1158 passes in one pixel */
1160 double minval = DNAN, maxval = DNAN;
1162 unsigned long gr_time;
1164 /* memory for the processed data */
1165 for (i = 0; i < im->gdes_c; i++) {
1166 if ((im->gdes[i].gf == GF_LINE) ||
1167 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1168 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1169 * sizeof(rrd_value_t))) == NULL) {
1170 rrd_set_error("malloc data_proc");
1176 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1179 gr_time = im->start + pixstep * i; /* time of the current step */
1182 for (ii = 0; ii < im->gdes_c; ii++) {
1185 switch (im->gdes[ii].gf) {
1189 if (!im->gdes[ii].stack)
1191 value = im->gdes[ii].yrule;
1192 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1193 /* The time of the data doesn't necessarily match
1194 ** the time of the graph. Beware.
1196 vidx = im->gdes[ii].vidx;
1197 if (im->gdes[vidx].gf == GF_VDEF) {
1198 value = im->gdes[vidx].vf.val;
1200 if (((long int) gr_time >=
1201 (long int) im->gdes[vidx].start)
1202 && ((long int) gr_time <=
1203 (long int) im->gdes[vidx].end)) {
1204 value = im->gdes[vidx].data[(unsigned long)
1210 im->gdes[vidx].step)
1211 * im->gdes[vidx].ds_cnt +
1218 if (!isnan(value)) {
1220 im->gdes[ii].p_data[i] = paintval;
1221 /* GF_TICK: the data values are not
1222 ** relevant for min and max
1224 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1225 if ((isnan(minval) || paintval < minval) &&
1226 !(im->logarithmic && paintval <= 0.0))
1228 if (isnan(maxval) || paintval > maxval)
1232 im->gdes[ii].p_data[i] = DNAN;
1237 ("STACK should already be turned into LINE or AREA here");
1246 /* if min or max have not been asigned a value this is because
1247 there was no data in the graph ... this is not good ...
1248 lets set these to dummy values then ... */
1250 if (im->logarithmic) {
1262 /* adjust min and max values */
1263 /* for logscale we add something on top */
1264 if (isnan(im->minval)
1265 || ((!im->rigid) && im->minval > minval)
1267 if (im->logarithmic)
1268 im->minval = minval * 0.5;
1270 im->minval = minval;
1272 if (isnan(im->maxval)
1273 || (!im->rigid && im->maxval < maxval)
1275 if (im->logarithmic)
1276 im->maxval = maxval * 2.0;
1278 im->maxval = maxval;
1281 /* make sure min is smaller than max */
1282 if (im->minval > im->maxval) {
1284 im->minval = 0.99 * im->maxval;
1286 im->minval = 1.01 * im->maxval;
1289 /* make sure min and max are not equal */
1290 if (AlmostEqual2sComplement(im->minval,im->maxval,4)) {
1296 /* make sure min and max are not both zero */
1297 if (AlmostEqual2sComplement(im->maxval,0,4)) {
1306 /* identify the point where the first gridline, label ... gets placed */
1308 time_t find_first_time(
1309 time_t start, /* what is the initial time */
1310 enum tmt_en baseint, /* what is the basic interval */
1311 long basestep /* how many if these do we jump a time */
1316 localtime_r(&start, &tm);
1320 tm. tm_sec -= tm.tm_sec % basestep;
1325 tm. tm_min -= tm.tm_min % basestep;
1331 tm. tm_hour -= tm.tm_hour % basestep;
1335 /* we do NOT look at the basestep for this ... */
1342 /* we do NOT look at the basestep for this ... */
1346 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1348 if (tm.tm_wday == 0)
1349 tm. tm_mday -= 7; /* we want the *previous* monday */
1357 tm. tm_mon -= tm.tm_mon % basestep;
1368 tm.tm_year + 1900) %basestep;
1374 /* identify the point where the next gridline, label ... gets placed */
1375 time_t find_next_time(
1376 time_t current, /* what is the initial time */
1377 enum tmt_en baseint, /* what is the basic interval */
1378 long basestep /* how many if these do we jump a time */
1384 localtime_r(¤t, &tm);
1389 tm. tm_sec += basestep;
1393 tm. tm_min += basestep;
1397 tm. tm_hour += basestep;
1401 tm. tm_mday += basestep;
1405 tm. tm_mday += 7 * basestep;
1409 tm. tm_mon += basestep;
1413 tm. tm_year += basestep;
1415 madetime = mktime(&tm);
1416 } while (madetime == -1); /* this is necessary to skip impssible times
1417 like the daylight saving time skips */
1423 /* calculate values required for PRINT and GPRINT functions */
1429 long i, ii, validsteps;
1432 int graphelement = 0;
1435 double magfact = -1;
1440 /* wow initializing tmvdef is quite a task :-) */
1441 time_t now = time(NULL);
1443 localtime_r(&now, &tmvdef);
1446 for (i = 0; i < im->gdes_c; i++) {
1447 vidx = im->gdes[i].vidx;
1448 switch (im->gdes[i].gf) {
1452 rrd_realloc((*prdata), prlines * sizeof(char *))) == NULL) {
1453 rrd_set_error("realloc prdata");
1457 /* PRINT and GPRINT can now print VDEF generated values.
1458 * There's no need to do any calculations on them as these
1459 * calculations were already made.
1461 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1462 printval = im->gdes[vidx].vf.val;
1463 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1464 } else { /* need to calculate max,min,avg etcetera */
1465 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1466 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1469 for (ii = im->gdes[vidx].ds;
1470 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1471 if (!finite(im->gdes[vidx].data[ii]))
1473 if (isnan(printval)) {
1474 printval = im->gdes[vidx].data[ii];
1479 switch (im->gdes[i].cf) {
1483 case CF_DEVSEASONAL:
1487 printval += im->gdes[vidx].data[ii];
1490 printval = min(printval, im->gdes[vidx].data[ii]);
1494 printval = max(printval, im->gdes[vidx].data[ii]);
1497 printval = im->gdes[vidx].data[ii];
1500 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1501 if (validsteps > 1) {
1502 printval = (printval / validsteps);
1505 } /* prepare printval */
1507 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1508 /* Magfact is set to -1 upon entry to print_calc. If it
1509 * is still less than 0, then we need to run auto_scale.
1510 * Otherwise, put the value into the correct units. If
1511 * the value is 0, then do not set the symbol or magnification
1512 * so next the calculation will be performed again. */
1513 if (magfact < 0.0) {
1514 auto_scale(im, &printval, &si_symb, &magfact);
1515 if (printval == 0.0)
1518 printval /= magfact;
1520 *(++percent_s) = 's';
1521 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1522 auto_scale(im, &printval, &si_symb, &magfact);
1525 if (im->gdes[i].gf == GF_PRINT) {
1526 (*prdata)[prlines - 2] =
1527 malloc((FMT_LEG_LEN + 2) * sizeof(char));
1528 (*prdata)[prlines - 1] = NULL;
1529 if (im->gdes[i].strftm) {
1530 strftime((*prdata)[prlines - 2], FMT_LEG_LEN,
1531 im->gdes[i].format, &tmvdef);
1533 if (bad_format(im->gdes[i].format)) {
1534 rrd_set_error("bad format for PRINT in '%s'",
1535 im->gdes[i].format);
1538 #ifdef HAVE_SNPRINTF
1539 snprintf((*prdata)[prlines - 2], FMT_LEG_LEN,
1540 im->gdes[i].format, printval, si_symb);
1542 sprintf((*prdata)[prlines - 2], im->gdes[i].format,
1549 if (im->gdes[i].strftm) {
1550 strftime(im->gdes[i].legend, FMT_LEG_LEN,
1551 im->gdes[i].format, &tmvdef);
1553 if (bad_format(im->gdes[i].format)) {
1554 rrd_set_error("bad format for GPRINT in '%s'",
1555 im->gdes[i].format);
1558 #ifdef HAVE_SNPRINTF
1559 snprintf(im->gdes[i].legend, FMT_LEG_LEN - 2,
1560 im->gdes[i].format, printval, si_symb);
1562 sprintf(im->gdes[i].legend, im->gdes[i].format, printval,
1575 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1576 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1581 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1582 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1591 #ifdef WITH_PIECHART
1599 ("STACK should already be turned into LINE or AREA here");
1604 return graphelement;
1608 /* place legends with color spots */
1614 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1615 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1616 int fill = 0, fill_last;
1618 int leg_x = border, leg_y = im->yimg;
1619 int leg_y_prev = im->yimg;
1622 int i, ii, mark = 0;
1623 char prt_fctn; /*special printfunctions */
1624 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1627 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1628 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1629 rrd_set_error("malloc for legspace");
1633 if (im->extra_flags & FULL_SIZE_MODE)
1634 leg_y = leg_y_prev =
1635 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1637 for (i = 0; i < im->gdes_c; i++) {
1640 /* hide legends for rules which are not displayed */
1642 if (im->gdes[i].gf == GF_TEXTALIGN) {
1643 default_txtalign = im->gdes[i].txtalign;
1646 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1647 if (im->gdes[i].gf == GF_HRULE &&
1648 (im->gdes[i].yrule < im->minval
1649 || im->gdes[i].yrule > im->maxval))
1650 im->gdes[i].legend[0] = '\0';
1652 if (im->gdes[i].gf == GF_VRULE &&
1653 (im->gdes[i].xrule < im->start
1654 || im->gdes[i].xrule > im->end))
1655 im->gdes[i].legend[0] = '\0';
1658 leg_cc = strlen(im->gdes[i].legend);
1660 /* is there a controle code ant the end of the legend string ? */
1661 /* and it is not a tab \\t */
1662 if (leg_cc >= 2 && im->gdes[i].legend[leg_cc - 2] == '\\'
1663 && im->gdes[i].legend[leg_cc - 1] != 't') {
1664 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1666 im->gdes[i].legend[leg_cc] = '\0';
1670 /* only valid control codes */
1671 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1676 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1678 rrd_set_error("Unknown control code at the end of '%s\\%c'",
1679 im->gdes[i].legend, prt_fctn);
1684 if (prt_fctn == 'n') {
1688 /* remove exess space from the end of the legend for \g */
1689 while (prt_fctn == 'g' &&
1690 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1692 im->gdes[i].legend[leg_cc] = '\0';
1697 /* no interleg space if string ends in \g */
1698 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1701 fill += legspace[i];
1703 fill += gfx_get_text_width(im, fill + border,
1704 im->text_prop[TEXT_PROP_LEGEND].
1706 im->text_prop[TEXT_PROP_LEGEND].
1708 im->gdes[i].legend);
1713 /* who said there was a special tag ... ? */
1714 if (prt_fctn == 'g') {
1718 if (prt_fctn == '\0') {
1719 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1720 /* just one legend item is left right or center */
1721 switch (default_txtalign) {
1736 /* is it time to place the legends ? */
1737 if (fill > im->ximg - 2 * border) {
1745 if (leg_c == 1 && prt_fctn == 'j') {
1751 if (prt_fctn != '\0') {
1753 if (leg_c >= 2 && prt_fctn == 'j') {
1754 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1758 if (prt_fctn == 'c')
1759 leg_x = (im->ximg - fill) / 2.0;
1760 if (prt_fctn == 'r')
1761 leg_x = im->ximg - fill - border;
1763 for (ii = mark; ii <= i; ii++) {
1764 if (im->gdes[ii].legend[0] == '\0')
1765 continue; /* skip empty legends */
1766 im->gdes[ii].leg_x = leg_x;
1767 im->gdes[ii].leg_y = leg_y;
1769 gfx_get_text_width(im, leg_x,
1770 im->text_prop[TEXT_PROP_LEGEND].
1772 im->text_prop[TEXT_PROP_LEGEND].
1774 im->gdes[ii].legend)
1779 if (im->extra_flags & FULL_SIZE_MODE) {
1780 /* only add y space if there was text on the line */
1781 if (leg_x > border || prt_fctn == 's')
1782 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1783 if (prt_fctn == 's')
1784 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1786 if (leg_x > border || prt_fctn == 's')
1787 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1788 if (prt_fctn == 's')
1789 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1797 if (im->extra_flags & FULL_SIZE_MODE) {
1798 if (leg_y != leg_y_prev) {
1799 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1801 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1804 im->yimg = leg_y_prev;
1805 /* if we did place some legends we have to add vertical space */
1806 if (leg_y != im->yimg)
1807 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1814 /* create a grid on the graph. it determines what to do
1815 from the values of xsize, start and end */
1817 /* the xaxis labels are determined from the number of seconds per pixel
1818 in the requested graph */
1822 int calc_horizontal_grid(
1829 int decimals, fractionals;
1831 im->ygrid_scale.labfact = 2;
1832 range = im->maxval - im->minval;
1833 scaledrange = range / im->magfact;
1835 /* does the scale of this graph make it impossible to put lines
1836 on it? If so, give up. */
1837 if (isnan(scaledrange)) {
1841 /* find grid spaceing */
1843 if (isnan(im->ygridstep)) {
1844 if (im->extra_flags & ALTYGRID) {
1845 /* find the value with max number of digits. Get number of digits */
1848 (max(fabs(im->maxval), fabs(im->minval)) *
1849 im->viewfactor / im->magfact));
1850 if (decimals <= 0) /* everything is small. make place for zero */
1853 im->ygrid_scale.gridstep =
1855 floor(log10(range * im->viewfactor / im->magfact))) /
1856 im->viewfactor * im->magfact;
1858 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1859 im->ygrid_scale.gridstep = 0.1;
1860 /* should have at least 5 lines but no more then 15 */
1861 if (range / im->ygrid_scale.gridstep < 5)
1862 im->ygrid_scale.gridstep /= 10;
1863 if (range / im->ygrid_scale.gridstep > 15)
1864 im->ygrid_scale.gridstep *= 10;
1865 if (range / im->ygrid_scale.gridstep > 5) {
1866 im->ygrid_scale.labfact = 1;
1867 if (range / im->ygrid_scale.gridstep > 8)
1868 im->ygrid_scale.labfact = 2;
1870 im->ygrid_scale.gridstep /= 5;
1871 im->ygrid_scale.labfact = 5;
1875 (im->ygrid_scale.gridstep *
1876 (double) im->ygrid_scale.labfact * im->viewfactor /
1878 if (fractionals < 0) { /* small amplitude. */
1879 int len = decimals - fractionals + 1;
1881 if (im->unitslength < len + 2)
1882 im->unitslength = len + 2;
1883 sprintf(im->ygrid_scale.labfmt, "%%%d.%df%s", len,
1884 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1886 int len = decimals + 1;
1888 if (im->unitslength < len + 2)
1889 im->unitslength = len + 2;
1890 sprintf(im->ygrid_scale.labfmt, "%%%d.0f%s", len,
1891 (im->symbol != ' ' ? " %c" : ""));
1894 for (i = 0; ylab[i].grid > 0; i++) {
1895 pixel = im->ysize / (scaledrange / ylab[i].grid);
1901 for (i = 0; i < 4; i++) {
1902 if (pixel * ylab[gridind].lfac[i] >=
1903 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1904 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1909 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1912 im->ygrid_scale.gridstep = im->ygridstep;
1913 im->ygrid_scale.labfact = im->ylabfact;
1918 int draw_horizontal_grid(
1923 char graph_label[100];
1925 double X0 = im->xorigin;
1926 double X1 = im->xorigin + im->xsize;
1928 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1929 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1933 im->ygrid_scale.gridstep / (double) im->magfact *
1934 (double) im->viewfactor;
1935 MaxY = scaledstep * (double) egrid;
1936 for (i = sgrid; i <= egrid; i++) {
1937 double Y0 = ytr(im, im->ygrid_scale.gridstep * i);
1938 double YN = ytr(im, im->ygrid_scale.gridstep * (i + 1));
1940 if (floor(Y0 + 0.5) >= im->yorigin - im->ysize
1941 && floor(Y0 + 0.5) <= im->yorigin) {
1942 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1943 with the chosen settings. Add a label if required by settings, or if
1944 there is only one label so far and the next grid line is out of bounds. */
1945 if (i % im->ygrid_scale.labfact == 0
1947 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1948 if (im->symbol == ' ') {
1949 if (im->extra_flags & ALTYGRID) {
1950 sprintf(graph_label, im->ygrid_scale.labfmt,
1951 scaledstep * (double) i);
1954 sprintf(graph_label, "%4.1f",
1955 scaledstep * (double) i);
1957 sprintf(graph_label, "%4.0f",
1958 scaledstep * (double) i);
1962 char sisym = (i == 0 ? ' ' : im->symbol);
1964 if (im->extra_flags & ALTYGRID) {
1965 sprintf(graph_label, im->ygrid_scale.labfmt,
1966 scaledstep * (double) i, sisym);
1969 sprintf(graph_label, "%4.1f %c",
1970 scaledstep * (double) i, sisym);
1972 sprintf(graph_label, "%4.0f %c",
1973 scaledstep * (double) i, sisym);
1980 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
1981 im->graph_col[GRC_FONT],
1982 im->text_prop[TEXT_PROP_AXIS].font,
1983 im->text_prop[TEXT_PROP_AXIS].size,
1984 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1988 X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1991 X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1995 MGRIDWIDTH, im->graph_col[GRC_MGRID],
1996 im->grid_dash_on, im->grid_dash_off);
1998 } else if (!(im->extra_flags & NOMINOR)) {
2001 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2004 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2008 GRIDWIDTH, im->graph_col[GRC_GRID],
2009 im->grid_dash_on, im->grid_dash_off);
2017 /* this is frexp for base 10 */
2028 iexp = floor(log(fabs(x)) / log(10));
2029 mnt = x / pow(10.0, iexp);
2032 mnt = x / pow(10.0, iexp);
2038 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2039 /* yes we are loosing precision by doing tos with floats instead of doubles
2040 but it seems more stable this way. */
2043 /* logaritmic horizontal grid */
2044 int horizontal_log_grid(
2047 double yloglab[][10] = {
2048 {1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2049 {1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
2050 {1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0},
2051 {1.0, 2.0, 4.0, 6.0, 8.0, 10., 0.0, 0.0, 0.0, 0.0},
2052 {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.},
2053 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2056 int i, j, val_exp, min_exp;
2057 double nex; /* number of decades in data */
2058 double logscale; /* scale in logarithmic space */
2059 int exfrac = 1; /* decade spacing */
2060 int mid = -1; /* row in yloglab for major grid */
2061 double mspac; /* smallest major grid spacing (pixels) */
2062 int flab; /* first value in yloglab to use */
2063 double value, tmp, pre_value;
2065 char graph_label[100];
2067 nex = log10(im->maxval / im->minval);
2068 logscale = im->ysize / nex;
2070 /* major spacing for data with high dynamic range */
2071 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2078 /* major spacing for less dynamic data */
2080 /* search best row in yloglab */
2082 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2083 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2084 } while (mspac > 2 * im->text_prop[TEXT_PROP_LEGEND].size
2085 && yloglab[mid][0] > 0);
2089 /* find first value in yloglab */
2091 yloglab[mid][flab] < 10
2092 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2093 if (yloglab[mid][flab] == 10.0) {
2098 if (val_exp % exfrac)
2099 val_exp += abs(-val_exp % exfrac);
2102 X1 = im->xorigin + im->xsize;
2108 value = yloglab[mid][flab] * pow(10.0, val_exp);
2109 if (AlmostEqual2sComplement(value, pre_value, 4))
2110 break; /* it seems we are not converging */
2114 Y0 = ytr(im, value);
2115 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2118 /* major grid line */
2121 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2123 X1, Y0, X1 + 2, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2129 MGRIDWIDTH, im->graph_col[GRC_MGRID],
2130 im->grid_dash_on, im->grid_dash_off);
2133 if (im->extra_flags & FORCE_UNITS_SI) {
2138 scale = floor(val_exp / 3.0);
2140 pvalue = pow(10.0, val_exp % 3);
2142 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2143 pvalue *= yloglab[mid][flab];
2145 if (((scale + si_symbcenter) < (int) sizeof(si_symbol)) &&
2146 ((scale + si_symbcenter) >= 0))
2147 symbol = si_symbol[scale + si_symbcenter];
2151 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2153 sprintf(graph_label, "%3.0e", value);
2155 X0 - im->text_prop[TEXT_PROP_AXIS].size, Y0,
2156 im->graph_col[GRC_FONT],
2157 im->text_prop[TEXT_PROP_AXIS].font,
2158 im->text_prop[TEXT_PROP_AXIS].size,
2159 im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2162 if (mid < 4 && exfrac == 1) {
2163 /* find first and last minor line behind current major line
2164 * i is the first line and j tha last */
2166 min_exp = val_exp - 1;
2167 for (i = 1; yloglab[mid][i] < 10.0; i++);
2168 i = yloglab[mid][i - 1] + 1;
2172 i = yloglab[mid][flab - 1] + 1;
2173 j = yloglab[mid][flab];
2176 /* draw minor lines below current major line */
2177 for (; i < j; i++) {
2179 value = i * pow(10.0, min_exp);
2180 if (value < im->minval)
2183 Y0 = ytr(im, value);
2184 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2190 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2193 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2197 GRIDWIDTH, im->graph_col[GRC_GRID],
2198 im->grid_dash_on, im->grid_dash_off);
2200 } else if (exfrac > 1) {
2201 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2202 value = pow(10.0, i);
2203 if (value < im->minval)
2206 Y0 = ytr(im, value);
2207 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2213 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2216 X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2220 GRIDWIDTH, im->graph_col[GRC_GRID],
2221 im->grid_dash_on, im->grid_dash_off);
2226 if (yloglab[mid][++flab] == 10.0) {
2232 /* draw minor lines after highest major line */
2233 if (mid < 4 && exfrac == 1) {
2234 /* find first and last minor line below current major line
2235 * i is the first line and j tha last */
2237 min_exp = val_exp - 1;
2238 for (i = 1; yloglab[mid][i] < 10.0; i++);
2239 i = yloglab[mid][i - 1] + 1;
2243 i = yloglab[mid][flab - 1] + 1;
2244 j = yloglab[mid][flab];
2247 /* draw minor lines below current major line */
2248 for (; i < j; i++) {
2250 value = i * pow(10.0, min_exp);
2251 if (value < im->minval)
2254 Y0 = ytr(im, value);
2255 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2260 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2262 X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2266 GRIDWIDTH, im->graph_col[GRC_GRID],
2267 im->grid_dash_on, im->grid_dash_off);
2270 /* fancy minor gridlines */
2271 else if (exfrac > 1) {
2272 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2273 value = pow(10.0, i);
2274 if (value < im->minval)
2277 Y0 = ytr(im, value);
2278 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2283 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2285 X1, Y0, X1 + 2, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2289 GRIDWIDTH, im->graph_col[GRC_GRID],
2290 im->grid_dash_on, im->grid_dash_off);
2301 int xlab_sel; /* which sort of label and grid ? */
2302 time_t ti, tilab, timajor;
2304 char graph_label[100];
2305 double X0, Y0, Y1; /* points for filled graph and more */
2308 /* the type of time grid is determined by finding
2309 the number of seconds per pixel in the graph */
2312 if (im->xlab_user.minsec == -1) {
2313 factor = (im->end - im->start) / im->xsize;
2315 while (xlab[xlab_sel + 1].minsec != -1
2316 && xlab[xlab_sel + 1].minsec <= factor) {
2318 } /* pick the last one */
2319 while (xlab[xlab_sel - 1].minsec == xlab[xlab_sel].minsec
2320 && xlab[xlab_sel].length > (im->end - im->start)) {
2322 } /* go back to the smallest size */
2323 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2324 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2325 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2326 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2327 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2328 im->xlab_user.labst = xlab[xlab_sel].labst;
2329 im->xlab_user.precis = xlab[xlab_sel].precis;
2330 im->xlab_user.stst = xlab[xlab_sel].stst;
2333 /* y coords are the same for every line ... */
2335 Y1 = im->yorigin - im->ysize;
2338 /* paint the minor grid */
2339 if (!(im->extra_flags & NOMINOR)) {
2340 for (ti = find_first_time(im->start,
2341 im->xlab_user.gridtm,
2342 im->xlab_user.gridst),
2343 timajor = find_first_time(im->start,
2344 im->xlab_user.mgridtm,
2345 im->xlab_user.mgridst);
2348 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2350 /* are we inside the graph ? */
2351 if (ti < im->start || ti > im->end)
2353 while (timajor < ti) {
2354 timajor = find_next_time(timajor,
2355 im->xlab_user.mgridtm,
2356 im->xlab_user.mgridst);
2359 continue; /* skip as falls on major grid line */
2361 gfx_line(im, X0, Y1 - 2, X0, Y1, GRIDWIDTH,
2362 im->graph_col[GRC_GRID]);
2363 gfx_line(im, X0, Y0, X0, Y0 + 2, GRIDWIDTH,
2364 im->graph_col[GRC_GRID]);
2365 gfx_dashed_line(im, X0, Y0 + 1, X0, Y1 - 1, GRIDWIDTH,
2366 im->graph_col[GRC_GRID],
2367 im->grid_dash_on, im->grid_dash_off);
2372 /* paint the major grid */
2373 for (ti = find_first_time(im->start,
2374 im->xlab_user.mgridtm,
2375 im->xlab_user.mgridst);
2377 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2379 /* are we inside the graph ? */
2380 if (ti < im->start || ti > im->end)
2383 gfx_line(im, X0, Y1 - 2, X0, Y1, MGRIDWIDTH,
2384 im->graph_col[GRC_MGRID]);
2385 gfx_line(im, X0, Y0, X0, Y0 + 3, MGRIDWIDTH,
2386 im->graph_col[GRC_MGRID]);
2387 gfx_dashed_line(im, X0, Y0 + 3, X0, Y1 - 2, MGRIDWIDTH,
2388 im->graph_col[GRC_MGRID],
2389 im->grid_dash_on, im->grid_dash_off);
2392 /* paint the labels below the graph */
2393 for (ti = find_first_time(im->start - im->xlab_user.precis / 2,
2394 im->xlab_user.labtm,
2395 im->xlab_user.labst);
2396 ti <= im->end - im->xlab_user.precis / 2;
2397 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2399 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2400 /* are we inside the graph ? */
2401 if (tilab < im->start || tilab > im->end)
2405 localtime_r(&tilab, &tm);
2406 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2408 # error "your libc has no strftime I guess we'll abort the exercise here."
2413 im->graph_col[GRC_FONT],
2414 im->text_prop[TEXT_PROP_AXIS].font,
2415 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 0.0,
2416 GFX_H_CENTER, GFX_V_TOP, graph_label);
2426 /* draw x and y axis */
2427 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2428 im->xorigin+im->xsize,im->yorigin-im->ysize,
2429 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2431 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2432 im->xorigin+im->xsize,im->yorigin-im->ysize,
2433 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2435 gfx_line(im, im->xorigin - 4, im->yorigin,
2436 im->xorigin + im->xsize + 4, im->yorigin,
2437 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2439 gfx_line(im, im->xorigin, im->yorigin + 4,
2440 im->xorigin, im->yorigin - im->ysize - 4,
2441 MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2444 /* arrow for X and Y axis direction */
2446 gfx_new_area(im, im->xorigin + im->xsize + 2, im->yorigin - 3, im->xorigin + im->xsize + 2, im->yorigin + 3, im->xorigin + im->xsize + 7, im->yorigin, /* horyzontal */
2447 im->graph_col[GRC_ARROW]);
2450 gfx_new_area(im, im->xorigin - 3, im->yorigin - im->ysize - 2, im->xorigin + 3, im->yorigin - im->ysize - 2, im->xorigin, im->yorigin - im->ysize - 7, /* vertical */
2451 im->graph_col[GRC_ARROW]);
2462 double X0, Y0; /* points for filled graph and more */
2463 struct gfx_color_t water_color;
2465 /* draw 3d border */
2466 gfx_new_area(im, 0, im->yimg,
2467 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2468 gfx_add_point(im, im->ximg - 2, 2);
2469 gfx_add_point(im, im->ximg, 0);
2470 gfx_add_point(im, 0, 0);
2473 gfx_new_area(im, 2, im->yimg - 2,
2474 im->ximg - 2, im->yimg - 2,
2475 im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2476 gfx_add_point(im, im->ximg, 0);
2477 gfx_add_point(im, im->ximg, im->yimg);
2478 gfx_add_point(im, 0, im->yimg);
2482 if (im->draw_x_grid == 1)
2485 if (im->draw_y_grid == 1) {
2486 if (im->logarithmic) {
2487 res = horizontal_log_grid(im);
2489 res = draw_horizontal_grid(im);
2492 /* dont draw horizontal grid if there is no min and max val */
2494 char *nodata = "No Data found";
2496 gfx_text(im, im->ximg / 2,
2497 (2 * im->yorigin - im->ysize) / 2,
2498 im->graph_col[GRC_FONT],
2499 im->text_prop[TEXT_PROP_AXIS].font,
2500 im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth,
2501 0.0, GFX_H_CENTER, GFX_V_CENTER, nodata);
2505 /* yaxis unit description */
2507 10, (im->yorigin - im->ysize / 2),
2508 im->graph_col[GRC_FONT],
2509 im->text_prop[TEXT_PROP_UNIT].font,
2510 im->text_prop[TEXT_PROP_UNIT].size, im->tabwidth,
2511 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2516 im->graph_col[GRC_FONT],
2517 im->text_prop[TEXT_PROP_TITLE].font,
2518 im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
2519 GFX_H_CENTER, GFX_V_TOP, im->title);
2520 /* rrdtool 'logo' */
2521 water_color = im->graph_col[GRC_FONT];
2522 water_color.alpha = 0.3;
2526 im->text_prop[TEXT_PROP_AXIS].font,
2527 5.5, im->tabwidth, -90,
2528 GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2530 /* graph watermark */
2531 if (im->watermark[0] != '\0') {
2533 im->ximg / 2, im->yimg - 6,
2535 im->text_prop[TEXT_PROP_AXIS].font,
2536 5.5, im->tabwidth, 0,
2537 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2541 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2542 for (i = 0; i < im->gdes_c; i++) {
2543 if (im->gdes[i].legend[0] == '\0')
2546 /* im->gdes[i].leg_y is the bottom of the legend */
2547 X0 = im->gdes[i].leg_x;
2548 Y0 = im->gdes[i].leg_y;
2549 gfx_text(im, X0, Y0,
2550 im->graph_col[GRC_FONT],
2551 im->text_prop[TEXT_PROP_LEGEND].font,
2552 im->text_prop[TEXT_PROP_LEGEND].size,
2553 im->tabwidth, 0.0, GFX_H_LEFT, GFX_V_BOTTOM,
2554 im->gdes[i].legend);
2555 /* The legend for GRAPH items starts with "M " to have
2556 enough space for the box */
2557 if (im->gdes[i].gf != GF_PRINT &&
2558 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2563 boxH = gfx_get_text_width(im, 0,
2564 im->text_prop[TEXT_PROP_LEGEND].
2566 im->text_prop[TEXT_PROP_LEGEND].
2567 size, im->tabwidth, "o") * 1.2;
2570 /* shift the box up a bit */
2573 /* make sure transparent colors show up the same way as in the graph */
2577 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2578 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2583 X0, Y0, X0 + boxH, Y0, im->gdes[i].col);
2584 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2588 cairo_new_path(im->cr);
2589 cairo_set_line_width(im->cr, 1.0);
2592 gfx_line_fit(im, &X0, &Y0);
2593 gfx_line_fit(im, &X1, &Y1);
2594 cairo_move_to(im->cr, X0, Y0);
2595 cairo_line_to(im->cr, X1, Y0);
2596 cairo_line_to(im->cr, X1, Y1);
2597 cairo_line_to(im->cr, X0, Y1);
2598 cairo_close_path(im->cr);
2599 cairo_set_source_rgba(im->cr, im->graph_col[GRC_FRAME].red,
2600 im->graph_col[GRC_FRAME].green,
2601 im->graph_col[GRC_FRAME].blue,
2602 im->graph_col[GRC_FRAME].alpha);
2603 cairo_stroke(im->cr);
2604 cairo_restore(im->cr);
2611 /*****************************************************
2612 * lazy check make sure we rely need to create this graph
2613 *****************************************************/
2620 struct stat imgstat;
2623 return 0; /* no lazy option */
2624 if (stat(im->graphfile, &imgstat) != 0)
2625 return 0; /* can't stat */
2626 /* one pixel in the existing graph is more then what we would
2628 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2630 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2631 return 0; /* the file does not exist */
2632 switch (im->imgformat) {
2634 size = PngSize(fd, &(im->ximg), &(im->yimg));
2644 int graph_size_location(
2648 /* The actual size of the image to draw is determined from
2649 ** several sources. The size given on the command line is
2650 ** the graph area but we need more as we have to draw labels
2651 ** and other things outside the graph area
2654 int Xvertical = 0, Ytitle = 0, Xylabel = 0, Xmain = 0, Ymain = 0,
2655 Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2657 if (im->extra_flags & ONLY_GRAPH) {
2659 im->ximg = im->xsize;
2660 im->yimg = im->ysize;
2661 im->yorigin = im->ysize;
2666 /** +---+--------------------------------------------+
2667 ** | y |...............graph title..................|
2668 ** | +---+-------------------------------+--------+
2671 ** | i | a | | pie |
2672 ** | s | x | main graph area | chart |
2677 ** | l | b +-------------------------------+--------+
2678 ** | e | l | x axis labels | |
2679 ** +---+---+-------------------------------+--------+
2680 ** |....................legends.....................|
2681 ** +------------------------------------------------+
2683 ** +------------------------------------------------+
2686 if (im->ylegend[0] != '\0') {
2687 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2690 if (im->title[0] != '\0') {
2691 /* The title is placed "inbetween" two text lines so it
2692 ** automatically has some vertical spacing. The horizontal
2693 ** spacing is added here, on each side.
2695 /* if necessary, reduce the font size of the title until it fits the image width */
2696 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2700 if (im->draw_x_grid) {
2701 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2703 if (im->draw_y_grid || im->forceleftspace) {
2704 Xylabel = gfx_get_text_width(im, 0,
2705 im->text_prop[TEXT_PROP_AXIS].font,
2706 im->text_prop[TEXT_PROP_AXIS].size,
2707 im->tabwidth, "0") * im->unitslength;
2711 if (im->extra_flags & FULL_SIZE_MODE) {
2712 /* The actual size of the image to draw has been determined by the user.
2713 ** The graph area is the space remaining after accounting for the legend,
2714 ** the watermark, the pie chart, the axis labels, and the title.
2717 im->ximg = im->xsize;
2718 im->yimg = im->ysize;
2719 im->yorigin = im->ysize;
2723 im->yorigin += Ytitle;
2725 /* Now calculate the total size. Insert some spacing where
2726 desired. im->xorigin and im->yorigin need to correspond
2727 with the lower left corner of the main graph area or, if
2728 this one is not set, the imaginary box surrounding the
2731 /* Initial size calculation for the main graph area */
2732 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2734 Xmain -= Xspacing; /* put space between main graph area and right edge */
2736 im->xorigin = Xspacing + Xylabel;
2738 /* the length of the title should not influence with width of the graph
2739 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2741 if (Xvertical) { /* unit description */
2743 im->xorigin += Xvertical;
2748 /* The vertical size of the image is known in advance. The main graph area
2749 ** (Ymain) and im->yorigin must be set according to the space requirements
2750 ** of the legend and the axis labels.
2753 if (im->extra_flags & NOLEGEND) {
2754 /* set dimensions correctly if using full size mode with no legend */
2756 im->yimg - im->text_prop[TEXT_PROP_AXIS].size * 2.5 -
2758 Ymain = im->yorigin;
2760 /* Determine where to place the legends onto the image.
2761 ** Set Ymain and adjust im->yorigin to match the space requirements.
2763 if (leg_place(im, &Ymain) == -1)
2768 /* remove title space *or* some padding above the graph from the main graph area */
2772 Ymain -= 1.5 * Yspacing;
2775 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2776 if (im->watermark[0] != '\0') {
2777 Ymain -= Ywatermark;
2782 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2784 /* The actual size of the image to draw is determined from
2785 ** several sources. The size given on the command line is
2786 ** the graph area but we need more as we have to draw labels
2787 ** and other things outside the graph area.
2790 if (im->ylegend[0] != '\0') {
2791 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2795 if (im->title[0] != '\0') {
2796 /* The title is placed "inbetween" two text lines so it
2797 ** automatically has some vertical spacing. The horizontal
2798 ** spacing is added here, on each side.
2800 /* don't care for the with of the title
2801 Xtitle = gfx_get_text_width(im->canvas, 0,
2802 im->text_prop[TEXT_PROP_TITLE].font,
2803 im->text_prop[TEXT_PROP_TITLE].size,
2805 im->title, 0) + 2*Xspacing; */
2806 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2813 /* Now calculate the total size. Insert some spacing where
2814 desired. im->xorigin and im->yorigin need to correspond
2815 with the lower left corner of the main graph area or, if
2816 this one is not set, the imaginary box surrounding the
2819 /* The legend width cannot yet be determined, as a result we
2820 ** have problems adjusting the image to it. For now, we just
2821 ** forget about it at all; the legend will have to fit in the
2822 ** size already allocated.
2824 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2827 im->ximg += Xspacing;
2829 im->xorigin = Xspacing + Xylabel;
2831 /* the length of the title should not influence with width of the graph
2832 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2834 if (Xvertical) { /* unit description */
2835 im->ximg += Xvertical;
2836 im->xorigin += Xvertical;
2840 /* The vertical size is interesting... we need to compare
2841 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2842 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2843 ** in order to start even thinking about Ylegend or Ywatermark.
2845 ** Do it in three portions: First calculate the inner part,
2846 ** then do the legend, then adjust the total height of the img,
2847 ** adding space for a watermark if one exists;
2850 /* reserve space for main and/or pie */
2852 im->yimg = Ymain + Yxlabel;
2855 im->yorigin = im->yimg - Yxlabel;
2857 /* reserve space for the title *or* some padding above the graph */
2860 im->yorigin += Ytitle;
2862 im->yimg += 1.5 * Yspacing;
2863 im->yorigin += 1.5 * Yspacing;
2865 /* reserve space for padding below the graph */
2866 im->yimg += Yspacing;
2868 /* Determine where to place the legends onto the image.
2869 ** Adjust im->yimg to match the space requirements.
2871 if (leg_place(im, 0) == -1)
2874 if (im->watermark[0] != '\0') {
2875 im->yimg += Ywatermark;
2885 static cairo_status_t cairo_write_func_filehandle(
2887 const unsigned char *data,
2888 unsigned int length)
2890 if (fwrite(data, length, 1, closure) != 1)
2891 return CAIRO_STATUS_WRITE_ERROR;
2892 return CAIRO_STATUS_SUCCESS;
2895 static cairo_status_t cairo_copy_to_buffer(
2897 const unsigned char *data,
2898 unsigned int length)
2900 image_desc_t *im = closure;
2902 im->rendered_image =
2903 realloc(im->rendered_image, im->rendered_image_size + length);
2904 if (im->rendered_image == NULL) {
2905 return CAIRO_STATUS_WRITE_ERROR;
2908 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2910 im->rendered_image_size += length;
2912 return CAIRO_STATUS_SUCCESS;
2915 /* draw that picture thing ... */
2921 int lazy = lazy_check(im);
2923 double areazero = 0.0;
2924 graph_desc_t *lastgdes = NULL;
2926 PangoFontMap *font_map = pango_cairo_font_map_get_default();
2929 /* if we are lazy and there is nothing to PRINT ... quit now */
2930 if (lazy && im->prt_c == 0)
2933 /* pull the data from the rrd files ... */
2935 if (data_fetch(im) == -1)
2938 /* evaluate VDEF and CDEF operations ... */
2939 if (data_calc(im) == -1)
2943 /* calculate and PRINT and GPRINT definitions. We have to do it at
2944 * this point because it will affect the length of the legends
2945 * if there are no graph elements we stop here ...
2946 * if we are lazy, try to quit ...
2948 i = print_calc(im, calcpr);
2951 if ((i == 0) || lazy)
2954 /**************************************************************
2955 *** Calculating sizes and locations became a bit confusing ***
2956 *** so I moved this into a separate function. ***
2957 **************************************************************/
2958 if (graph_size_location(im, i) == -1)
2961 /* get actual drawing data and find min and max values */
2962 if (data_proc(im) == -1)
2965 if (!im->logarithmic) {
2968 /* identify si magnitude Kilo, Mega Giga ? */
2969 if (!im->rigid && !im->logarithmic)
2970 expand_range(im); /* make sure the upper and lower limit are
2973 if (!calc_horizontal_grid(im))
2980 apply_gridfit(im); */
2983 /* the actual graph is created by going through the individual
2984 graph elements and then drawing them */
2985 cairo_surface_destroy(im->surface);
2987 switch (im->imgformat) {
2990 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
2991 im->ximg * im->zoom,
2992 im->yimg * im->zoom);
2996 im->surface = strlen(im->graphfile)
2997 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
2998 im->yimg * im->zoom)
2999 : cairo_pdf_surface_create_for_stream(&cairo_copy_to_buffer, im,
3000 im->ximg * im->zoom,
3001 im->yimg * im->zoom);
3005 im->surface = strlen(im->graphfile)
3006 ? cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3007 im->yimg * im->zoom)
3008 : cairo_ps_surface_create_for_stream(&cairo_copy_to_buffer, im,
3009 im->ximg * im->zoom,
3010 im->yimg * im->zoom);
3014 im->surface = strlen(im->graphfile)
3015 ? cairo_svg_surface_create(im->graphfile, im->ximg * im->zoom,
3016 im->yimg * im->zoom)
3017 : cairo_svg_surface_create_for_stream(&cairo_copy_to_buffer, im,
3018 im->ximg * im->zoom,
3019 im->yimg * im->zoom);
3020 cairo_svg_surface_restrict_to_version(im->surface,
3021 CAIRO_SVG_VERSION_1_1);
3024 im->cr = cairo_create(im->surface);
3025 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3026 cairo_set_antialias(im->cr, im->graph_antialias);
3027 cairo_scale(im->cr, im->zoom, im->zoom);
3031 0, im->yimg, im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3033 gfx_add_point(im, im->ximg, 0);
3037 im->xorigin, im->yorigin,
3038 im->xorigin + im->xsize, im->yorigin,
3039 im->xorigin + im->xsize, im->yorigin - im->ysize,
3040 im->graph_col[GRC_CANVAS]);
3042 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3045 if (im->minval > 0.0)
3046 areazero = im->minval;
3047 if (im->maxval < 0.0)
3048 areazero = im->maxval;
3050 for (i = 0; i < im->gdes_c; i++) {
3051 switch (im->gdes[i].gf) {
3065 for (ii = 0; ii < im->xsize; ii++) {
3066 if (!isnan(im->gdes[i].p_data[ii]) &&
3067 im->gdes[i].p_data[ii] != 0.0) {
3068 if (im->gdes[i].yrule > 0) {
3070 im->xorigin + ii, im->yorigin,
3073 im->gdes[i].yrule * im->ysize, 1.0,
3075 } else if (im->gdes[i].yrule < 0) {
3078 im->yorigin - im->ysize,
3081 im->gdes[i].yrule) *
3082 im->ysize, 1.0, im->gdes[i].col);
3090 /* fix data points at oo and -oo */
3091 for (ii = 0; ii < im->xsize; ii++) {
3092 if (isinf(im->gdes[i].p_data[ii])) {
3093 if (im->gdes[i].p_data[ii] > 0) {
3094 im->gdes[i].p_data[ii] = im->maxval;
3096 im->gdes[i].p_data[ii] = im->minval;
3102 /* *******************************************************
3107 -------|--t-1--t--------------------------------
3109 if we know the value at time t was a then
3110 we draw a square from t-1 to t with the value a.
3112 ********************************************************* */
3113 if (im->gdes[i].col.alpha != 0.0) {
3114 /* GF_LINE and friend */
3115 if (im->gdes[i].gf == GF_LINE) {
3116 double last_y = 0.0;
3120 cairo_new_path(im->cr);
3122 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3123 for (ii = 1; ii < im->xsize; ii++) {
3124 if (isnan(im->gdes[i].p_data[ii])
3125 || (im->slopemode == 1
3126 && isnan(im->gdes[i].p_data[ii - 1]))) {
3131 last_y = ytr(im, im->gdes[i].p_data[ii]);
3132 if (im->slopemode == 0) {
3133 double x = ii - 1 + im->xorigin;
3136 gfx_line_fit(im, &x, &y);
3137 cairo_move_to(im->cr, x, y);
3138 x = ii + im->xorigin;
3140 gfx_line_fit(im, &x, &y);
3141 cairo_line_to(im->cr, x, y);
3143 double x = ii - 1 + im->xorigin;
3145 im->gdes[i].p_data[ii - 1]);
3147 gfx_line_fit(im, &x, &y);
3148 cairo_move_to(im->cr, x, y);
3149 x = ii + im->xorigin;
3151 gfx_line_fit(im, &x, &y);
3152 cairo_line_to(im->cr, x, y);
3156 double x1 = ii + im->xorigin;
3157 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3159 if (im->slopemode == 0
3160 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3161 double x = ii - 1 + im->xorigin;
3164 gfx_line_fit(im, &x, &y);
3165 cairo_line_to(im->cr, x, y);
3168 gfx_line_fit(im, &x1, &y1);
3169 cairo_line_to(im->cr, x1, y1);
3173 cairo_set_source_rgba(im->cr, im->gdes[i].col.red,
3174 im->gdes[i].col.green,
3175 im->gdes[i].col.blue,
3176 im->gdes[i].col.alpha);
3177 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3178 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3179 cairo_stroke(im->cr);
3180 cairo_restore(im->cr);
3183 double *foreY = malloc(sizeof(double) * im->xsize * 2);
3184 double *foreX = malloc(sizeof(double) * im->xsize * 2);
3185 double *backY = malloc(sizeof(double) * im->xsize * 2);
3186 double *backX = malloc(sizeof(double) * im->xsize * 2);
3189 for (ii = 0; ii <= im->xsize; ii++) {
3192 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3197 && AlmostEqual2sComplement(foreY[lastI],
3199 && AlmostEqual2sComplement(foreY[lastI],
3207 foreX[cntI], foreY[cntI],
3209 while (cntI < idxI) {
3214 AlmostEqual2sComplement(foreY[lastI],
3217 AlmostEqual2sComplement(foreY[lastI],
3222 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3224 gfx_add_point(im, backX[idxI], backY[idxI]);
3230 AlmostEqual2sComplement(backY[lastI],
3233 AlmostEqual2sComplement(backY[lastI],
3238 gfx_add_point(im, backX[idxI], backY[idxI]);
3248 if (ii == im->xsize)
3251 if (im->slopemode == 0 && ii == 0) {
3254 if (isnan(im->gdes[i].p_data[ii])) {
3258 ytop = ytr(im, im->gdes[i].p_data[ii]);
3259 if (lastgdes && im->gdes[i].stack) {
3260 ybase = ytr(im, lastgdes->p_data[ii]);
3262 ybase = ytr(im, areazero);
3264 if (ybase == ytop) {
3270 double extra = ytop;
3275 if (im->slopemode == 0) {
3276 backY[++idxI] = ybase - 0.2;
3277 backX[idxI] = ii + im->xorigin - 1;
3278 foreY[idxI] = ytop + 0.2;
3279 foreX[idxI] = ii + im->xorigin - 1;
3281 backY[++idxI] = ybase - 0.2;
3282 backX[idxI] = ii + im->xorigin;
3283 foreY[idxI] = ytop + 0.2;
3284 foreX[idxI] = ii + im->xorigin;
3286 /* close up any remaining area */
3291 } /* else GF_LINE */
3293 /* if color != 0x0 */
3294 /* make sure we do not run into trouble when stacking on NaN */
3295 for (ii = 0; ii < im->xsize; ii++) {
3296 if (isnan(im->gdes[i].p_data[ii])) {
3297 if (lastgdes && (im->gdes[i].stack)) {
3298 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3300 im->gdes[i].p_data[ii] = areazero;
3304 lastgdes = &(im->gdes[i]);
3308 ("STACK should already be turned into LINE or AREA here");
3315 /* grid_paint also does the text */
3316 if (!(im->extra_flags & ONLY_GRAPH))
3320 if (!(im->extra_flags & ONLY_GRAPH))
3323 /* the RULES are the last thing to paint ... */
3324 for (i = 0; i < im->gdes_c; i++) {
3326 switch (im->gdes[i].gf) {
3328 if (im->gdes[i].yrule >= im->minval
3329 && im->gdes[i].yrule <= im->maxval)
3331 im->xorigin, ytr(im, im->gdes[i].yrule),
3332 im->xorigin + im->xsize, ytr(im,
3334 1.0, im->gdes[i].col);
3337 if (im->gdes[i].xrule >= im->start
3338 && im->gdes[i].xrule <= im->end)
3340 xtr(im, im->gdes[i].xrule), im->yorigin,
3341 xtr(im, im->gdes[i].xrule),
3342 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3350 switch (im->imgformat) {
3353 cairo_status_t status;
3355 if (strlen(im->graphfile) == 0) {
3357 cairo_surface_write_to_png_stream(im->surface,
3358 &cairo_copy_to_buffer, im);
3359 } else if (strcmp(im->graphfile, "-") == 0) {
3361 cairo_surface_write_to_png_stream(im->surface,
3362 &cairo_write_func_filehandle,
3365 status = cairo_surface_write_to_png(im->surface, im->graphfile);
3368 if (status != CAIRO_STATUS_SUCCESS) {
3369 rrd_set_error("Could not save png to '%s'", im->graphfile);
3375 if (strlen(im->graphfile)) {
3376 cairo_show_page(im->cr);
3378 cairo_surface_finish(im->surface);
3386 /*****************************************************
3388 *****************************************************/
3395 if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
3396 * sizeof(graph_desc_t))) ==
3398 rrd_set_error("realloc graph_descs");
3403 im->gdes[im->gdes_c - 1].step = im->step;
3404 im->gdes[im->gdes_c - 1].step_orig = im->step;
3405 im->gdes[im->gdes_c - 1].stack = 0;
3406 im->gdes[im->gdes_c - 1].linewidth = 0;
3407 im->gdes[im->gdes_c - 1].debug = 0;
3408 im->gdes[im->gdes_c - 1].start = im->start;
3409 im->gdes[im->gdes_c - 1].start_orig = im->start;
3410 im->gdes[im->gdes_c - 1].end = im->end;
3411 im->gdes[im->gdes_c - 1].end_orig = im->end;
3412 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3413 im->gdes[im->gdes_c - 1].data = NULL;
3414 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3415 im->gdes[im->gdes_c - 1].data_first = 0;
3416 im->gdes[im->gdes_c - 1].p_data = NULL;
3417 im->gdes[im->gdes_c - 1].rpnp = NULL;
3418 im->gdes[im->gdes_c - 1].shift = 0.0;
3419 im->gdes[im->gdes_c - 1].col.red = 0.0;
3420 im->gdes[im->gdes_c - 1].col.green = 0.0;
3421 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3422 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3423 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3424 im->gdes[im->gdes_c - 1].format[0] = '\0';
3425 im->gdes[im->gdes_c - 1].strftm = 0;
3426 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3427 im->gdes[im->gdes_c - 1].ds = -1;
3428 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3429 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3430 im->gdes[im->gdes_c - 1].p_data = NULL;
3431 im->gdes[im->gdes_c - 1].yrule = DNAN;
3432 im->gdes[im->gdes_c - 1].xrule = 0;
3436 /* copies input untill the first unescaped colon is found
3437 or until input ends. backslashes have to be escaped as well */
3439 const char *const input,
3445 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3446 if (input[inp] == '\\' &&
3447 input[inp + 1] != '\0' &&
3448 (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3449 output[outp++] = input[++inp];
3451 output[outp++] = input[inp];
3454 output[outp] = '\0';
3458 /* Some surgery done on this function, it became ridiculously big.
3460 ** - initializing now in rrd_graph_init()
3461 ** - options parsing now in rrd_graph_options()
3462 ** - script parsing now in rrd_graph_script()
3476 rrd_graph_init(&im);
3478 /* a dummy surface so that we can measure text sizes for placements */
3479 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3480 im.cr = cairo_create(im.surface);
3481 im.graphhandle = stream;
3483 rrd_graph_options(argc, argv, &im);
3484 if (rrd_test_error()) {
3489 if (optind >= argc) {
3490 rrd_set_error("missing filename");
3494 if (strlen(argv[optind]) >= MAXPATH) {
3495 rrd_set_error("filename (including path) too long");
3500 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3501 im.graphfile[MAXPATH - 1] = '\0';
3503 rrd_graph_script(argc, argv, &im, 1);
3504 if (rrd_test_error()) {
3509 /* Everything is now read and the actual work can start */
3512 if (graph_paint(&im, prdata) == -1) {
3517 /* The image is generated and needs to be output.
3518 ** Also, if needed, print a line with information about the image.
3529 /* maybe prdata is not allocated yet ... lets do it now */
3530 if ((*prdata = calloc(2, sizeof(char *))) == NULL) {
3531 rrd_set_error("malloc imginfo");
3536 malloc((strlen(im.imginfo) + 200 +
3537 strlen(im.graphfile)) * sizeof(char)))
3539 rrd_set_error("malloc imginfo");
3542 filename = im.graphfile + strlen(im.graphfile);
3543 while (filename > im.graphfile) {
3544 if (*(filename - 1) == '/' || *(filename - 1) == '\\')
3549 sprintf((*prdata)[0], im.imginfo, filename,
3550 (long) (im.zoom * im.ximg), (long) (im.zoom * im.yimg));
3556 /* a simplified version of the above that just creates the graph in memory
3557 and returns a pointer to it. */
3559 unsigned char *rrd_graph_in_memory(
3571 rrd_graph_init(&im);
3573 /* a dummy surface so that we can measure text sizes for placements */
3574 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3575 im.cr = cairo_create(im.surface);
3577 rrd_graph_options(argc, argv, &im);
3578 if (rrd_test_error()) {
3583 rrd_graph_script(argc, argv, &im, 1);
3584 if (rrd_test_error()) {
3589 /* Everything is now read and the actual work can start */
3591 /* by not assigning a name to im.graphfile data will be written to
3592 newly allocated memory on im.rendered_image ... */
3595 if (graph_paint(&im, prdata) == -1) {
3604 *img_size = im.rendered_image_size;
3607 return im.rendered_image;
3610 void rrd_graph_init(
3618 #ifdef HAVE_SETLOCALE
3619 setlocale(LC_TIME, "");
3620 #ifdef HAVE_MBSTOWCS
3621 setlocale(LC_CTYPE, "");
3627 im->xlab_user.minsec = -1;
3632 im->rendered_image_size = 0;
3633 im->rendered_image = NULL;
3635 im->ylegend[0] = '\0';
3636 im->title[0] = '\0';
3637 im->watermark[0] = '\0';
3640 im->unitsexponent = 9999;
3641 im->unitslength = 6;
3642 im->forceleftspace = 0;
3644 im->viewfactor = 1.0;
3645 im->imgformat = IF_PNG;
3646 im->graphfile[0] = '\0';
3649 im->extra_flags = 0;
3655 im->logarithmic = 0;
3656 im->ygridstep = DNAN;
3657 im->draw_x_grid = 1;
3658 im->draw_y_grid = 1;
3663 im->grid_dash_on = 1;
3664 im->grid_dash_off = 1;
3665 im->tabwidth = 40.0;
3667 im->font_options = cairo_font_options_create();
3668 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3670 cairo_font_options_set_hint_style(im->font_options,
3671 CAIRO_HINT_STYLE_FULL);
3672 cairo_font_options_set_hint_metrics(im->font_options,
3673 CAIRO_HINT_METRICS_ON);
3674 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3677 for (i = 0; i < DIM(graph_col); i++)
3678 im->graph_col[i] = graph_col[i];
3680 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3683 char rrd_win_default_font[1000];
3685 windir = getenv("windir");
3686 /* %windir% is something like D:\windows or C:\winnt */
3687 if (windir != NULL) {
3688 strncpy(rrd_win_default_font, windir, 500);
3689 rrd_win_default_font[500] = '\0';
3690 strcat(rrd_win_default_font, "\\fonts\\");
3691 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3692 for (i = 0; i < DIM(text_prop); i++) {
3693 strncpy(text_prop[i].font, rrd_win_default_font,
3694 sizeof(text_prop[i].font) - 1);
3695 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3703 deffont = getenv("RRD_DEFAULT_FONT");
3704 if (deffont != NULL) {
3705 for (i = 0; i < DIM(text_prop); i++) {
3706 strncpy(text_prop[i].font, deffont,
3707 sizeof(text_prop[i].font) - 1);
3708 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3712 for (i = 0; i < DIM(text_prop); i++) {
3713 im->text_prop[i].size = text_prop[i].size;
3714 strcpy(im->text_prop[i].font, text_prop[i].font);
3718 void rrd_graph_options(
3724 char *parsetime_error = NULL;
3725 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3726 time_t start_tmp = 0, end_tmp = 0;
3728 struct rrd_time_value start_tv, end_tv;
3729 long unsigned int color;
3730 char *old_locale = "";
3732 /* defines for long options without a short equivalent. should be bytes,
3733 and may not collide with (the ASCII value of) short options */
3734 #define LONGOPT_UNITS_SI 255
3735 struct option long_options[] = {
3736 {"start", required_argument, 0, 's'},
3737 {"end", required_argument, 0, 'e'},
3738 {"x-grid", required_argument, 0, 'x'},
3739 {"y-grid", required_argument, 0, 'y'},
3740 {"vertical-label", required_argument, 0, 'v'},
3741 {"width", required_argument, 0, 'w'},
3742 {"height", required_argument, 0, 'h'},
3743 {"full-size-mode", no_argument, 0, 'D'},
3744 {"interlaced", no_argument, 0, 'i'},
3745 {"upper-limit", required_argument, 0, 'u'},
3746 {"lower-limit", required_argument, 0, 'l'},
3747 {"rigid", no_argument, 0, 'r'},
3748 {"base", required_argument, 0, 'b'},
3749 {"logarithmic", no_argument, 0, 'o'},
3750 {"color", required_argument, 0, 'c'},
3751 {"font", required_argument, 0, 'n'},
3752 {"title", required_argument, 0, 't'},
3753 {"imginfo", required_argument, 0, 'f'},
3754 {"imgformat", required_argument, 0, 'a'},
3755 {"lazy", no_argument, 0, 'z'},
3756 {"zoom", required_argument, 0, 'm'},
3757 {"no-legend", no_argument, 0, 'g'},
3758 {"force-rules-legend", no_argument, 0, 'F'},
3759 {"only-graph", no_argument, 0, 'j'},
3760 {"alt-y-grid", no_argument, 0, 'Y'},
3761 {"no-minor", no_argument, 0, 'I'},
3762 {"slope-mode", no_argument, 0, 'E'},
3763 {"alt-autoscale", no_argument, 0, 'A'},
3764 {"alt-autoscale-min", no_argument, 0, 'J'},
3765 {"alt-autoscale-max", no_argument, 0, 'M'},
3766 {"no-gridfit", no_argument, 0, 'N'},
3767 {"units-exponent", required_argument, 0, 'X'},
3768 {"units-length", required_argument, 0, 'L'},
3769 {"units", required_argument, 0, LONGOPT_UNITS_SI},
3770 {"step", required_argument, 0, 'S'},
3771 {"tabwidth", required_argument, 0, 'T'},
3772 {"font-render-mode", required_argument, 0, 'R'},
3773 {"graph-render-mode", required_argument, 0, 'G'},
3774 {"font-smoothing-threshold", required_argument, 0, 'B'},
3775 {"watermark", required_argument, 0, 'W'},
3776 {"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 */
3781 opterr = 0; /* initialize getopt */
3783 parsetime("end-24h", &start_tv);
3784 parsetime("now", &end_tv);
3787 int option_index = 0;
3789 int col_start, col_end;
3791 opt = getopt_long(argc, argv,
3792 "s:e:x:y:v:w:h:D:iu:l:rb:oc:n:m:t:f:a:I:zgjFYAMEX:L:S:T:NR:B:W:",
3793 long_options, &option_index);
3800 im->extra_flags |= NOMINOR;
3803 im->extra_flags |= ALTYGRID;
3806 im->extra_flags |= ALTAUTOSCALE;
3809 im->extra_flags |= ALTAUTOSCALE_MIN;
3812 im->extra_flags |= ALTAUTOSCALE_MAX;
3815 im->extra_flags |= ONLY_GRAPH;
3818 im->extra_flags |= NOLEGEND;
3821 im->extra_flags |= FORCE_RULES_LEGEND;
3823 case LONGOPT_UNITS_SI:
3824 if (im->extra_flags & FORCE_UNITS) {
3825 rrd_set_error("--units can only be used once!");
3826 setlocale(LC_NUMERIC, old_locale);
3829 if (strcmp(optarg, "si") == 0)
3830 im->extra_flags |= FORCE_UNITS_SI;
3832 rrd_set_error("invalid argument for --units: %s", optarg);
3837 im->unitsexponent = atoi(optarg);
3840 im->unitslength = atoi(optarg);
3841 im->forceleftspace = 1;
3844 old_locale = setlocale(LC_NUMERIC, "C");
3845 im->tabwidth = atof(optarg);
3846 setlocale(LC_NUMERIC, old_locale);
3849 old_locale = setlocale(LC_NUMERIC, "C");
3850 im->step = atoi(optarg);
3851 setlocale(LC_NUMERIC, old_locale);
3857 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3858 rrd_set_error("start time: %s", parsetime_error);
3863 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3864 rrd_set_error("end time: %s", parsetime_error);
3869 if (strcmp(optarg, "none") == 0) {
3870 im->draw_x_grid = 0;
3875 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
3877 &im->xlab_user.gridst,
3879 &im->xlab_user.mgridst,
3881 &im->xlab_user.labst,
3882 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
3883 strncpy(im->xlab_form, optarg + stroff,
3884 sizeof(im->xlab_form) - 1);
3885 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
3886 if ((int) (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
3887 rrd_set_error("unknown keyword %s", scan_gtm);
3889 } else if ((int) (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
3891 rrd_set_error("unknown keyword %s", scan_mtm);
3893 } else if ((int) (im->xlab_user.labtm = tmt_conv(scan_ltm)) ==
3895 rrd_set_error("unknown keyword %s", scan_ltm);
3898 im->xlab_user.minsec = 1;
3899 im->xlab_user.stst = im->xlab_form;
3901 rrd_set_error("invalid x-grid format");
3907 if (strcmp(optarg, "none") == 0) {
3908 im->draw_y_grid = 0;
3911 old_locale = setlocale(LC_NUMERIC, "C");
3912 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
3913 setlocale(LC_NUMERIC, old_locale);
3914 if (im->ygridstep <= 0) {
3915 rrd_set_error("grid step must be > 0");
3917 } else if (im->ylabfact < 1) {
3918 rrd_set_error("label factor must be > 0");
3922 setlocale(LC_NUMERIC, old_locale);
3923 rrd_set_error("invalid y-grid format");
3928 strncpy(im->ylegend, optarg, 150);
3929 im->ylegend[150] = '\0';
3932 old_locale = setlocale(LC_NUMERIC, "C");
3933 im->maxval = atof(optarg);
3934 setlocale(LC_NUMERIC, old_locale);
3937 old_locale = setlocale(LC_NUMERIC, "C");
3938 im->minval = atof(optarg);
3939 setlocale(LC_NUMERIC, old_locale);
3942 im->base = atol(optarg);
3943 if (im->base != 1024 && im->base != 1000) {
3945 ("the only sensible value for base apart from 1000 is 1024");
3950 long_tmp = atol(optarg);
3951 if (long_tmp < 10) {
3952 rrd_set_error("width below 10 pixels");
3955 im->xsize = long_tmp;
3958 long_tmp = atol(optarg);
3959 if (long_tmp < 10) {
3960 rrd_set_error("height below 10 pixels");
3963 im->ysize = long_tmp;
3966 im->extra_flags |= FULL_SIZE_MODE;
3969 /* interlaced png not supported at the moment */
3975 im->imginfo = optarg;
3978 if ((int) (im->imgformat = if_conv(optarg)) == -1) {
3979 rrd_set_error("unsupported graphics format '%s'", optarg);
3991 im->logarithmic = 1;
3995 "%10[A-Z]#%n%8lx%n",
3996 col_nam, &col_start, &color, &col_end) == 2) {
3998 int col_len = col_end - col_start;
4002 color = (((color & 0xF00) * 0x110000) |
4003 ((color & 0x0F0) * 0x011000) |
4004 ((color & 0x00F) * 0x001100) | 0x000000FF);
4007 color = (((color & 0xF000) * 0x11000) |
4008 ((color & 0x0F00) * 0x01100) |
4009 ((color & 0x00F0) * 0x00110) |
4010 ((color & 0x000F) * 0x00011)
4014 color = (color << 8) + 0xff /* shift left by 8 */ ;
4019 rrd_set_error("the color format is #RRGGBB[AA]");
4022 if ((ci = grc_conv(col_nam)) != -1) {
4023 im->graph_col[ci] = gfx_hex_to_col(color);
4025 rrd_set_error("invalid color name '%s'", col_nam);
4029 rrd_set_error("invalid color def format");
4036 char font[1024] = "";
4038 old_locale = setlocale(LC_NUMERIC, "C");
4039 if (sscanf(optarg, "%10[A-Z]:%lf:%1000s", prop, &size, font) >= 2) {
4040 int sindex, propidx;
4042 setlocale(LC_NUMERIC, old_locale);
4043 if ((sindex = text_prop_conv(prop)) != -1) {
4044 for (propidx = sindex; propidx < TEXT_PROP_LAST;
4047 im->text_prop[propidx].size = size;
4049 if (strlen(font) > 0) {
4050 strcpy(im->text_prop[propidx].font, font);
4052 if (propidx == sindex && sindex != 0)
4056 rrd_set_error("invalid fonttag '%s'", prop);
4060 setlocale(LC_NUMERIC, old_locale);
4061 rrd_set_error("invalid text property format");
4067 old_locale = setlocale(LC_NUMERIC, "C");
4068 im->zoom = atof(optarg);
4069 setlocale(LC_NUMERIC, old_locale);
4070 if (im->zoom <= 0.0) {
4071 rrd_set_error("zoom factor must be > 0");
4076 strncpy(im->title, optarg, 150);
4077 im->title[150] = '\0';
4081 if (strcmp(optarg, "normal") == 0) {
4082 cairo_font_options_set_antialias(im->font_options,
4083 CAIRO_ANTIALIAS_GRAY);
4084 cairo_font_options_set_hint_style(im->font_options,
4085 CAIRO_HINT_STYLE_FULL);
4086 } else if (strcmp(optarg, "light") == 0) {
4087 cairo_font_options_set_antialias(im->font_options,
4088 CAIRO_ANTIALIAS_GRAY);
4089 cairo_font_options_set_hint_style(im->font_options,
4090 CAIRO_HINT_STYLE_SLIGHT);
4091 } else if (strcmp(optarg, "mono") == 0) {
4092 cairo_font_options_set_antialias(im->font_options,
4093 CAIRO_ANTIALIAS_NONE);
4094 cairo_font_options_set_hint_style(im->font_options,
4095 CAIRO_HINT_STYLE_FULL);
4097 rrd_set_error("unknown font-render-mode '%s'", optarg);
4102 if (strcmp(optarg, "normal") == 0)
4103 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4104 else if (strcmp(optarg, "mono") == 0)
4105 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4107 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4112 /* not supported curently */
4116 strncpy(im->watermark, optarg, 100);
4117 im->watermark[99] = '\0';
4122 rrd_set_error("unknown option '%c'", optopt);
4124 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4129 if (im->logarithmic == 1 && im->minval <= 0) {
4131 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4135 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4136 /* error string is set in parsetime.c */
4140 if (start_tmp < 3600 * 24 * 365 * 10) {
4141 rrd_set_error("the first entry to fetch should be after 1980 (%ld)",
4146 if (end_tmp < start_tmp) {
4147 rrd_set_error("start (%ld) should be less than end (%ld)",
4148 start_tmp, end_tmp);
4152 im->start = start_tmp;
4154 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4157 int rrd_graph_color(
4164 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4166 color = strstr(var, "#");
4167 if (color == NULL) {
4168 if (optional == 0) {
4169 rrd_set_error("Found no color in %s", err);
4176 long unsigned int col;
4178 rest = strstr(color, ":");
4186 sscanf(color, "#%6lx%n", &col, &n);
4187 col = (col << 8) + 0xff /* shift left by 8 */ ;
4189 rrd_set_error("Color problem in %s", err);
4192 sscanf(color, "#%8lx%n", &col, &n);
4196 rrd_set_error("Color problem in %s", err);
4198 if (rrd_test_error())
4200 gdp->col = gfx_hex_to_col(col);
4213 while (*ptr != '\0')
4214 if (*ptr++ == '%') {
4216 /* line cannot end with percent char */
4220 /* '%s', '%S' and '%%' are allowed */
4221 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4224 /* %c is allowed (but use only with vdef!) */
4225 else if (*ptr == 'c') {
4230 /* or else '% 6.2lf' and such are allowed */
4232 /* optional padding character */
4233 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4236 /* This should take care of 'm.n' with all three optional */
4237 while (*ptr >= '0' && *ptr <= '9')
4241 while (*ptr >= '0' && *ptr <= '9')
4244 /* Either 'le', 'lf' or 'lg' must follow here */
4247 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4260 struct graph_desc_t *gdes,
4261 const char *const str)
4263 /* A VDEF currently is either "func" or "param,func"
4264 * so the parsing is rather simple. Change if needed.
4272 old_locale = setlocale(LC_NUMERIC, "C");
4273 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4274 setlocale(LC_NUMERIC, old_locale);
4275 if (n == (int) strlen(str)) { /* matched */
4279 sscanf(str, "%29[A-Z]%n", func, &n);
4280 if (n == (int) strlen(str)) { /* matched */
4283 rrd_set_error("Unknown function string '%s' in VDEF '%s'", str,
4288 if (!strcmp("PERCENT", func))
4289 gdes->vf.op = VDEF_PERCENT;
4290 else if (!strcmp("MAXIMUM", func))
4291 gdes->vf.op = VDEF_MAXIMUM;
4292 else if (!strcmp("AVERAGE", func))
4293 gdes->vf.op = VDEF_AVERAGE;
4294 else if (!strcmp("STDEV", func))
4295 gdes->vf.op = VDEF_STDEV;
4296 else if (!strcmp("MINIMUM", func))
4297 gdes->vf.op = VDEF_MINIMUM;
4298 else if (!strcmp("TOTAL", func))
4299 gdes->vf.op = VDEF_TOTAL;
4300 else if (!strcmp("FIRST", func))
4301 gdes->vf.op = VDEF_FIRST;
4302 else if (!strcmp("LAST", func))
4303 gdes->vf.op = VDEF_LAST;
4304 else if (!strcmp("LSLSLOPE", func))
4305 gdes->vf.op = VDEF_LSLSLOPE;
4306 else if (!strcmp("LSLINT", func))
4307 gdes->vf.op = VDEF_LSLINT;
4308 else if (!strcmp("LSLCORREL", func))
4309 gdes->vf.op = VDEF_LSLCORREL;
4311 rrd_set_error("Unknown function '%s' in VDEF '%s'\n", func,
4316 switch (gdes->vf.op) {
4318 if (isnan(param)) { /* no parameter given */
4319 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n",
4323 if (param >= 0.0 && param <= 100.0) {
4324 gdes->vf.param = param;
4325 gdes->vf.val = DNAN; /* undefined */
4326 gdes->vf.when = 0; /* undefined */
4328 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n", param,
4342 case VDEF_LSLCORREL:
4344 gdes->vf.param = DNAN;
4345 gdes->vf.val = DNAN;
4348 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n",
4362 graph_desc_t *src, *dst;
4366 dst = &im->gdes[gdi];
4367 src = &im->gdes[dst->vidx];
4368 data = src->data + src->ds;
4369 steps = (src->end - src->start) / src->step;
4372 printf("DEBUG: start == %lu, end == %lu, %lu steps\n", src->start,
4376 switch (dst->vf.op) {
4382 if ((array = malloc(steps * sizeof(double))) == NULL) {
4383 rrd_set_error("malloc VDEV_PERCENT");
4386 for (step = 0; step < steps; step++) {
4387 array[step] = data[step * src->ds_cnt];
4389 qsort(array, step, sizeof(double), vdef_percent_compar);
4391 field = (steps - 1) * dst->vf.param / 100;
4392 dst->vf.val = array[field];
4393 dst->vf.when = 0; /* no time component */
4396 for (step = 0; step < steps; step++)
4397 printf("DEBUG: %3li:%10.2f %c\n", step, array[step],
4398 step == field ? '*' : ' ');
4404 while (step != steps && isnan(data[step * src->ds_cnt]))
4406 if (step == steps) {
4410 dst->vf.val = data[step * src->ds_cnt];
4411 dst->vf.when = src->start + (step + 1) * src->step;
4413 while (step != steps) {
4414 if (finite(data[step * src->ds_cnt])) {
4415 if (data[step * src->ds_cnt] > dst->vf.val) {
4416 dst->vf.val = data[step * src->ds_cnt];
4417 dst->vf.when = src->start + (step + 1) * src->step;
4428 double average = 0.0;
4430 for (step = 0; step < steps; step++) {
4431 if (finite(data[step * src->ds_cnt])) {
4432 sum += data[step * src->ds_cnt];
4437 if (dst->vf.op == VDEF_TOTAL) {
4438 dst->vf.val = sum * src->step;
4439 dst->vf.when = 0; /* no time component */
4440 } else if (dst->vf.op == VDEF_AVERAGE) {
4441 dst->vf.val = sum / cnt;
4442 dst->vf.when = 0; /* no time component */
4444 average = sum / cnt;
4446 for (step = 0; step < steps; step++) {
4447 if (finite(data[step * src->ds_cnt])) {
4448 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4451 dst->vf.val = pow(sum / cnt, 0.5);
4452 dst->vf.when = 0; /* no time component */
4462 while (step != steps && isnan(data[step * src->ds_cnt]))
4464 if (step == steps) {
4468 dst->vf.val = data[step * src->ds_cnt];
4469 dst->vf.when = src->start + (step + 1) * src->step;
4471 while (step != steps) {
4472 if (finite(data[step * src->ds_cnt])) {
4473 if (data[step * src->ds_cnt] < dst->vf.val) {
4474 dst->vf.val = data[step * src->ds_cnt];
4475 dst->vf.when = src->start + (step + 1) * src->step;
4482 /* The time value returned here is one step before the
4483 * actual time value. This is the start of the first
4487 while (step != steps && isnan(data[step * src->ds_cnt]))
4489 if (step == steps) { /* all entries were NaN */
4493 dst->vf.val = data[step * src->ds_cnt];
4494 dst->vf.when = src->start + step * src->step;
4498 /* The time value returned here is the
4499 * actual time value. This is the end of the last
4503 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4505 if (step < 0) { /* all entries were NaN */
4509 dst->vf.val = data[step * src->ds_cnt];
4510 dst->vf.when = src->start + (step + 1) * src->step;
4515 case VDEF_LSLCORREL:{
4516 /* Bestfit line by linear least squares method */
4519 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4527 for (step = 0; step < steps; step++) {
4528 if (finite(data[step * src->ds_cnt])) {
4531 SUMxx += step * step;
4532 SUMxy += step * data[step * src->ds_cnt];
4533 SUMy += data[step * src->ds_cnt];
4534 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4538 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4539 y_intercept = (SUMy - slope * SUMx) / cnt;
4542 (SUMx * SUMy) / cnt) / sqrt((SUMxx -
4543 (SUMx * SUMx) / cnt) * (SUMyy -
4549 if (dst->vf.op == VDEF_LSLSLOPE) {
4550 dst->vf.val = slope;
4552 } else if (dst->vf.op == VDEF_LSLINT) {
4553 dst->vf.val = y_intercept;
4555 } else if (dst->vf.op == VDEF_LSLCORREL) {
4556 dst->vf.val = correl;
4570 /* NaN < -INF < finite_values < INF */
4571 int vdef_percent_compar(
4575 /* Equality is not returned; this doesn't hurt except
4576 * (maybe) for a little performance.
4579 /* First catch NaN values. They are smallest */
4580 if (isnan(*(double *) a))
4582 if (isnan(*(double *) b))
4585 /* NaN doesn't reach this part so INF and -INF are extremes.
4586 * The sign from isinf() is compatible with the sign we return
4588 if (isinf(*(double *) a))
4589 return isinf(*(double *) a);
4590 if (isinf(*(double *) b))
4591 return isinf(*(double *) b);
4593 /* If we reach this, both values must be finite */
4594 if (*(double *) a < *(double *) b)