1 /****************************************************************************
2 * RRDtool 1.3rc2 Copyright by Tobi Oetiker, 1997-2008
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 "DejaVu Sans Mono,Bitstream Vera Sans Mono,monospace,Courier"
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, 6, 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 allocated memory used for dashed lines */
326 if (im->gdes[i].p_dashes != NULL)
327 free(im->gdes[i].p_dashes);
329 free(im->gdes[i].p_data);
330 free(im->gdes[i].rpnp);
333 if (im->font_options)
334 cairo_font_options_destroy(im->font_options);
337 status = cairo_status(im->cr);
338 cairo_destroy(im->cr);
340 if (im->rendered_image) {
341 free(im->rendered_image);
344 cairo_surface_destroy(im->surface);
346 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
347 cairo_status_to_string(status));
352 /* find SI magnitude symbol for the given number*/
354 image_desc_t *im, /* image description */
360 char *symbol[] = { "a", /* 10e-18 Atto */
361 "f", /* 10e-15 Femto */
362 "p", /* 10e-12 Pico */
363 "n", /* 10e-9 Nano */
364 "u", /* 10e-6 Micro */
365 "m", /* 10e-3 Milli */
370 "T", /* 10e12 Tera */
371 "P", /* 10e15 Peta */
378 if (*value == 0.0 || isnan(*value)) {
382 sindex = floor(log(fabs(*value)) / log((double) im->base));
383 *magfact = pow((double) im->base, (double) sindex);
384 (*value) /= (*magfact);
386 if (sindex <= symbcenter && sindex >= -symbcenter) {
387 (*symb_ptr) = symbol[sindex + symbcenter];
394 static char si_symbol[] = {
395 'a', /* 10e-18 Atto */
396 'f', /* 10e-15 Femto */
397 'p', /* 10e-12 Pico */
398 'n', /* 10e-9 Nano */
399 'u', /* 10e-6 Micro */
400 'm', /* 10e-3 Milli */
405 'T', /* 10e12 Tera */
406 'P', /* 10e15 Peta */
409 static const int si_symbcenter = 6;
411 /* find SI magnitude symbol for the numbers on the y-axis*/
413 image_desc_t *im /* image description */
417 double digits, viewdigits = 0;
420 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
421 log((double) im->base));
423 if (im->unitsexponent != 9999) {
424 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
425 viewdigits = floor(im->unitsexponent / 3);
430 im->magfact = pow((double) im->base, digits);
433 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
436 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
438 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
439 ((viewdigits + si_symbcenter) >= 0))
440 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
445 /* move min and max values around to become sensible */
450 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
451 600.0, 500.0, 400.0, 300.0, 250.0,
452 200.0, 125.0, 100.0, 90.0, 80.0,
453 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
454 25.0, 20.0, 10.0, 9.0, 8.0,
455 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
456 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
457 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
460 double scaled_min, scaled_max;
467 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
468 im->minval, im->maxval, im->magfact);
471 if (isnan(im->ygridstep)) {
472 if (im->extra_flags & ALTAUTOSCALE) {
473 /* measure the amplitude of the function. Make sure that
474 graph boundaries are slightly higher then max/min vals
475 so we can see amplitude on the graph */
478 delt = im->maxval - im->minval;
480 fact = 2.0 * pow(10.0,
482 (max(fabs(im->minval), fabs(im->maxval)) /
485 adj = (fact - delt) * 0.55;
488 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
489 im->minval, im->maxval, delt, fact, adj);
494 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
495 /* measure the amplitude of the function. Make sure that
496 graph boundaries are slightly lower than min vals
497 so we can see amplitude on the graph */
498 adj = (im->maxval - im->minval) * 0.1;
500 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
501 /* measure the amplitude of the function. Make sure that
502 graph boundaries are slightly higher than max vals
503 so we can see amplitude on the graph */
504 adj = (im->maxval - im->minval) * 0.1;
507 scaled_min = im->minval / im->magfact;
508 scaled_max = im->maxval / im->magfact;
510 for (i = 1; sensiblevalues[i] > 0; i++) {
511 if (sensiblevalues[i - 1] >= scaled_min &&
512 sensiblevalues[i] <= scaled_min)
513 im->minval = sensiblevalues[i] * (im->magfact);
515 if (-sensiblevalues[i - 1] <= scaled_min &&
516 -sensiblevalues[i] >= scaled_min)
517 im->minval = -sensiblevalues[i - 1] * (im->magfact);
519 if (sensiblevalues[i - 1] >= scaled_max &&
520 sensiblevalues[i] <= scaled_max)
521 im->maxval = sensiblevalues[i - 1] * (im->magfact);
523 if (-sensiblevalues[i - 1] <= scaled_max &&
524 -sensiblevalues[i] >= scaled_max)
525 im->maxval = -sensiblevalues[i] * (im->magfact);
529 /* adjust min and max to the grid definition if there is one */
530 im->minval = (double) im->ylabfact * im->ygridstep *
531 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
532 im->maxval = (double) im->ylabfact * im->ygridstep *
533 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
537 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
538 im->minval, im->maxval, im->magfact);
546 if (isnan(im->minval) || isnan(im->maxval))
549 if (im->logarithmic) {
550 double ya, yb, ypix, ypixfrac;
551 double log10_range = log10(im->maxval) - log10(im->minval);
553 ya = pow((double) 10, floor(log10(im->minval)));
554 while (ya < im->minval)
557 return; /* don't have y=10^x gridline */
559 if (yb <= im->maxval) {
560 /* we have at least 2 y=10^x gridlines.
561 Make sure distance between them in pixels
562 are an integer by expanding im->maxval */
563 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
564 double factor = y_pixel_delta / floor(y_pixel_delta);
565 double new_log10_range = factor * log10_range;
566 double new_ymax_log10 = log10(im->minval) + new_log10_range;
568 im->maxval = pow(10, new_ymax_log10);
569 ytr(im, DNAN); /* reset precalc */
570 log10_range = log10(im->maxval) - log10(im->minval);
572 /* make sure first y=10^x gridline is located on
573 integer pixel position by moving scale slightly
574 downwards (sub-pixel movement) */
575 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
576 ypixfrac = ypix - floor(ypix);
577 if (ypixfrac > 0 && ypixfrac < 1) {
578 double yfrac = ypixfrac / im->ysize;
580 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
581 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
582 ytr(im, DNAN); /* reset precalc */
585 /* Make sure we have an integer pixel distance between
586 each minor gridline */
587 double ypos1 = ytr(im, im->minval);
588 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
589 double y_pixel_delta = ypos1 - ypos2;
590 double factor = y_pixel_delta / floor(y_pixel_delta);
591 double new_range = factor * (im->maxval - im->minval);
592 double gridstep = im->ygrid_scale.gridstep;
593 double minor_y, minor_y_px, minor_y_px_frac;
595 if (im->maxval > 0.0)
596 im->maxval = im->minval + new_range;
598 im->minval = im->maxval - new_range;
599 ytr(im, DNAN); /* reset precalc */
600 /* make sure first minor gridline is on integer pixel y coord */
601 minor_y = gridstep * floor(im->minval / gridstep);
602 while (minor_y < im->minval)
604 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
605 minor_y_px_frac = minor_y_px - floor(minor_y_px);
606 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
607 double yfrac = minor_y_px_frac / im->ysize;
608 double range = im->maxval - im->minval;
610 im->minval = im->minval - yfrac * range;
611 im->maxval = im->maxval - yfrac * range;
612 ytr(im, DNAN); /* reset precalc */
614 calc_horizontal_grid(im); /* recalc with changed im->maxval */
618 /* reduce data reimplementation by Alex */
621 enum cf_en cf, /* which consolidation function ? */
622 unsigned long cur_step, /* step the data currently is in */
623 time_t *start, /* start, end and step as requested ... */
624 time_t *end, /* ... by the application will be ... */
625 unsigned long *step, /* ... adjusted to represent reality */
626 unsigned long *ds_cnt, /* number of data sources in file */
628 { /* two dimensional array containing the data */
629 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
630 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
632 rrd_value_t *srcptr, *dstptr;
634 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
637 row_cnt = ((*end) - (*start)) / cur_step;
643 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
644 row_cnt, reduce_factor, *start, *end, cur_step);
645 for (col = 0; col < row_cnt; col++) {
646 printf("time %10lu: ", *start + (col + 1) * cur_step);
647 for (i = 0; i < *ds_cnt; i++)
648 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
653 /* We have to combine [reduce_factor] rows of the source
654 ** into one row for the destination. Doing this we also
655 ** need to take care to combine the correct rows. First
656 ** alter the start and end time so that they are multiples
657 ** of the new step time. We cannot reduce the amount of
658 ** time so we have to move the end towards the future and
659 ** the start towards the past.
661 end_offset = (*end) % (*step);
662 start_offset = (*start) % (*step);
664 /* If there is a start offset (which cannot be more than
665 ** one destination row), skip the appropriate number of
666 ** source rows and one destination row. The appropriate
667 ** number is what we do know (start_offset/cur_step) of
668 ** the new interval (*step/cur_step aka reduce_factor).
671 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
672 printf("row_cnt before: %lu\n", row_cnt);
675 (*start) = (*start) - start_offset;
676 skiprows = reduce_factor - start_offset / cur_step;
677 srcptr += skiprows * *ds_cnt;
678 for (col = 0; col < (*ds_cnt); col++)
683 printf("row_cnt between: %lu\n", row_cnt);
686 /* At the end we have some rows that are not going to be
687 ** used, the amount is end_offset/cur_step
690 (*end) = (*end) - end_offset + (*step);
691 skiprows = end_offset / cur_step;
695 printf("row_cnt after: %lu\n", row_cnt);
698 /* Sanity check: row_cnt should be multiple of reduce_factor */
699 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
701 if (row_cnt % reduce_factor) {
702 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
703 row_cnt, reduce_factor);
704 printf("BUG in reduce_data()\n");
708 /* Now combine reduce_factor intervals at a time
709 ** into one interval for the destination.
712 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
713 for (col = 0; col < (*ds_cnt); col++) {
714 rrd_value_t newval = DNAN;
715 unsigned long validval = 0;
717 for (i = 0; i < reduce_factor; i++) {
718 if (isnan(srcptr[i * (*ds_cnt) + col])) {
723 newval = srcptr[i * (*ds_cnt) + col];
732 newval += srcptr[i * (*ds_cnt) + col];
735 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
738 /* an interval contains a failure if any subintervals contained a failure */
740 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
743 newval = srcptr[i * (*ds_cnt) + col];
769 srcptr += (*ds_cnt) * reduce_factor;
770 row_cnt -= reduce_factor;
772 /* If we had to alter the endtime, we didn't have enough
773 ** source rows to fill the last row. Fill it with NaN.
776 for (col = 0; col < (*ds_cnt); col++)
779 row_cnt = ((*end) - (*start)) / *step;
781 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
782 row_cnt, *start, *end, *step);
783 for (col = 0; col < row_cnt; col++) {
784 printf("time %10lu: ", *start + (col + 1) * (*step));
785 for (i = 0; i < *ds_cnt; i++)
786 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
793 /* get the data required for the graphs from the
802 /* pull the data from the rrd files ... */
803 for (i = 0; i < (int) im->gdes_c; i++) {
804 /* only GF_DEF elements fetch data */
805 if (im->gdes[i].gf != GF_DEF)
809 /* do we have it already ? */
810 for (ii = 0; ii < i; ii++) {
811 if (im->gdes[ii].gf != GF_DEF)
813 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
814 && (im->gdes[i].cf == im->gdes[ii].cf)
815 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
816 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
817 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
818 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
819 /* OK, the data is already there.
820 ** Just copy the header portion
822 im->gdes[i].start = im->gdes[ii].start;
823 im->gdes[i].end = im->gdes[ii].end;
824 im->gdes[i].step = im->gdes[ii].step;
825 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
826 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
827 im->gdes[i].data = im->gdes[ii].data;
828 im->gdes[i].data_first = 0;
835 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
837 if ((rrd_fetch_fn(im->gdes[i].rrd,
843 &im->gdes[i].ds_namv,
844 &im->gdes[i].data)) == -1) {
847 im->gdes[i].data_first = 1;
849 if (ft_step < im->gdes[i].step) {
850 reduce_data(im->gdes[i].cf_reduce,
855 &im->gdes[i].ds_cnt, &im->gdes[i].data);
857 im->gdes[i].step = ft_step;
861 /* lets see if the required data source is really there */
862 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
863 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
867 if (im->gdes[i].ds == -1) {
868 rrd_set_error("No DS called '%s' in '%s'",
869 im->gdes[i].ds_nam, im->gdes[i].rrd);
877 /* evaluate the expressions in the CDEF functions */
879 /*************************************************************
881 *************************************************************/
883 long find_var_wrapper(
887 return find_var((image_desc_t *) arg1, key);
890 /* find gdes containing var*/
897 for (ii = 0; ii < im->gdes_c - 1; ii++) {
898 if ((im->gdes[ii].gf == GF_DEF
899 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
900 && (strcmp(im->gdes[ii].vname, key) == 0)) {
907 /* find the largest common denominator for all the numbers
908 in the 0 terminated num array */
915 for (i = 0; num[i + 1] != 0; i++) {
917 rest = num[i] % num[i + 1];
923 /* return i==0?num[i]:num[i-1]; */
927 /* run the rpn calculator on all the VDEF and CDEF arguments */
934 long *steparray, rpi;
939 rpnstack_init(&rpnstack);
941 for (gdi = 0; gdi < im->gdes_c; gdi++) {
942 /* Look for GF_VDEF and GF_CDEF in the same loop,
943 * so CDEFs can use VDEFs and vice versa
945 switch (im->gdes[gdi].gf) {
949 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
951 /* remove current shift */
952 vdp->start -= vdp->shift;
953 vdp->end -= vdp->shift;
956 if (im->gdes[gdi].shidx >= 0)
957 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
960 vdp->shift = im->gdes[gdi].shval;
962 /* normalize shift to multiple of consolidated step */
963 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
966 vdp->start += vdp->shift;
967 vdp->end += vdp->shift;
971 /* A VDEF has no DS. This also signals other parts
972 * of rrdtool that this is a VDEF value, not a CDEF.
974 im->gdes[gdi].ds_cnt = 0;
975 if (vdef_calc(im, gdi)) {
976 rrd_set_error("Error processing VDEF '%s'",
977 im->gdes[gdi].vname);
978 rpnstack_free(&rpnstack);
983 im->gdes[gdi].ds_cnt = 1;
984 im->gdes[gdi].ds = 0;
985 im->gdes[gdi].data_first = 1;
986 im->gdes[gdi].start = 0;
987 im->gdes[gdi].end = 0;
992 /* Find the variables in the expression.
993 * - VDEF variables are substituted by their values
994 * and the opcode is changed into OP_NUMBER.
995 * - CDEF variables are analized for their step size,
996 * the lowest common denominator of all the step
997 * sizes of the data sources involved is calculated
998 * and the resulting number is the step size for the
999 * resulting data source.
1001 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1002 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1003 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1004 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1006 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
1009 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1010 im->gdes[gdi].vname, im->gdes[ptr].vname);
1011 printf("DEBUG: value from vdef is %f\n",
1012 im->gdes[ptr].vf.val);
1014 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1015 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1016 } else { /* normal variables and PREF(variables) */
1018 /* add one entry to the array that keeps track of the step sizes of the
1019 * data sources going into the CDEF. */
1021 rrd_realloc(steparray,
1023 1) * sizeof(*steparray))) == NULL) {
1024 rrd_set_error("realloc steparray");
1025 rpnstack_free(&rpnstack);
1029 steparray[stepcnt - 1] = im->gdes[ptr].step;
1031 /* adjust start and end of cdef (gdi) so
1032 * that it runs from the latest start point
1033 * to the earliest endpoint of any of the
1034 * rras involved (ptr)
1037 if (im->gdes[gdi].start < im->gdes[ptr].start)
1038 im->gdes[gdi].start = im->gdes[ptr].start;
1040 if (im->gdes[gdi].end == 0 ||
1041 im->gdes[gdi].end > im->gdes[ptr].end)
1042 im->gdes[gdi].end = im->gdes[ptr].end;
1044 /* store pointer to the first element of
1045 * the rra providing data for variable,
1046 * further save step size and data source
1049 im->gdes[gdi].rpnp[rpi].data =
1050 im->gdes[ptr].data + im->gdes[ptr].ds;
1051 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1052 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1054 /* backoff the *.data ptr; this is done so
1055 * rpncalc() function doesn't have to treat
1056 * the first case differently
1058 } /* if ds_cnt != 0 */
1059 } /* if OP_VARIABLE */
1060 } /* loop through all rpi */
1062 /* move the data pointers to the correct period */
1063 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1064 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1065 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1066 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1068 im->gdes[gdi].start - im->gdes[ptr].start;
1071 im->gdes[gdi].rpnp[rpi].data +=
1072 (diff / im->gdes[ptr].step) *
1073 im->gdes[ptr].ds_cnt;
1077 if (steparray == NULL) {
1078 rrd_set_error("rpn expressions without DEF"
1079 " or CDEF variables are not supported");
1080 rpnstack_free(&rpnstack);
1083 steparray[stepcnt] = 0;
1084 /* Now find the resulting step. All steps in all
1085 * used RRAs have to be visited
1087 im->gdes[gdi].step = lcd(steparray);
1089 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1090 im->gdes[gdi].start)
1091 / im->gdes[gdi].step)
1092 * sizeof(double))) == NULL) {
1093 rrd_set_error("malloc im->gdes[gdi].data");
1094 rpnstack_free(&rpnstack);
1098 /* Step through the new cdef results array and
1099 * calculate the values
1101 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1102 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1103 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1105 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1106 * in this case we are advancing by timesteps;
1107 * we use the fact that time_t is a synonym for long
1109 if (rpn_calc(rpnp, &rpnstack, (long) now,
1110 im->gdes[gdi].data, ++dataidx) == -1) {
1111 /* rpn_calc sets the error string */
1112 rpnstack_free(&rpnstack);
1115 } /* enumerate over time steps within a CDEF */
1120 } /* enumerate over CDEFs */
1121 rpnstack_free(&rpnstack);
1125 static int AlmostEqual2sComplement(
1131 int aInt = *(int *) &A;
1132 int bInt = *(int *) &B;
1135 /* Make sure maxUlps is non-negative and small enough that the
1136 default NAN won't compare as equal to anything. */
1138 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1140 /* Make aInt lexicographically ordered as a twos-complement int */
1143 aInt = 0x80000000l - aInt;
1145 /* Make bInt lexicographically ordered as a twos-complement int */
1148 bInt = 0x80000000l - bInt;
1150 intDiff = abs(aInt - bInt);
1152 if (intDiff <= maxUlps)
1158 /* massage data so, that we get one value for each x coordinate in the graph */
1163 double pixstep = (double) (im->end - im->start)
1164 / (double) im->xsize; /* how much time
1165 passes in one pixel */
1167 double minval = DNAN, maxval = DNAN;
1169 unsigned long gr_time;
1171 /* memory for the processed data */
1172 for (i = 0; i < im->gdes_c; i++) {
1173 if ((im->gdes[i].gf == GF_LINE) ||
1174 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1175 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1176 * sizeof(rrd_value_t))) == NULL) {
1177 rrd_set_error("malloc data_proc");
1183 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1186 gr_time = im->start + pixstep * i; /* time of the current step */
1189 for (ii = 0; ii < im->gdes_c; ii++) {
1192 switch (im->gdes[ii].gf) {
1196 if (!im->gdes[ii].stack)
1198 value = im->gdes[ii].yrule;
1199 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1200 /* The time of the data doesn't necessarily match
1201 ** the time of the graph. Beware.
1203 vidx = im->gdes[ii].vidx;
1204 if (im->gdes[vidx].gf == GF_VDEF) {
1205 value = im->gdes[vidx].vf.val;
1207 if (((long int) gr_time >=
1208 (long int) im->gdes[vidx].start)
1209 && ((long int) gr_time <=
1210 (long int) im->gdes[vidx].end)) {
1211 value = im->gdes[vidx].data[(unsigned long)
1217 im->gdes[vidx].step)
1218 * im->gdes[vidx].ds_cnt +
1225 if (!isnan(value)) {
1227 im->gdes[ii].p_data[i] = paintval;
1228 /* GF_TICK: the data values are not
1229 ** relevant for min and max
1231 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1232 if ((isnan(minval) || paintval < minval) &&
1233 !(im->logarithmic && paintval <= 0.0))
1235 if (isnan(maxval) || paintval > maxval)
1239 im->gdes[ii].p_data[i] = DNAN;
1244 ("STACK should already be turned into LINE or AREA here");
1253 /* if min or max have not been asigned a value this is because
1254 there was no data in the graph ... this is not good ...
1255 lets set these to dummy values then ... */
1257 if (im->logarithmic) {
1258 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1259 minval = 0.0; /* catching this right away below */
1262 /* in logarithm mode, where minval is smaller or equal
1263 to 0 make the beast just way smaller than maxval */
1265 minval = maxval / 10e8;
1268 if (isnan(minval) || isnan(maxval)) {
1274 /* adjust min and max values given by the user*/
1275 /* for logscale we add something on top */
1276 if (isnan(im->minval)
1277 || ((!im->rigid) && im->minval > minval)
1279 if (im->logarithmic)
1280 im->minval = minval / 2.0;
1282 im->minval = minval;
1284 if (isnan(im->maxval)
1285 || (!im->rigid && im->maxval < maxval)
1287 if (im->logarithmic)
1288 im->maxval = maxval * 2.0;
1290 im->maxval = maxval;
1293 /* make sure min is smaller than max */
1294 if (im->minval > im->maxval) {
1296 im->minval = 0.99 * im->maxval;
1298 im->minval = 1.01 * im->maxval;
1301 /* make sure min and max are not equal */
1302 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1308 /* make sure min and max are not both zero */
1309 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1318 /* identify the point where the first gridline, label ... gets placed */
1320 time_t find_first_time(
1321 time_t start, /* what is the initial time */
1322 enum tmt_en baseint, /* what is the basic interval */
1323 long basestep /* how many if these do we jump a time */
1328 localtime_r(&start, &tm);
1332 tm. tm_sec -= tm.tm_sec % basestep;
1337 tm. tm_min -= tm.tm_min % basestep;
1343 tm. tm_hour -= tm.tm_hour % basestep;
1347 /* we do NOT look at the basestep for this ... */
1354 /* we do NOT look at the basestep for this ... */
1358 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1360 if (tm.tm_wday == 0)
1361 tm. tm_mday -= 7; /* we want the *previous* monday */
1369 tm. tm_mon -= tm.tm_mon % basestep;
1380 tm.tm_year + 1900) %basestep;
1386 /* identify the point where the next gridline, label ... gets placed */
1387 time_t find_next_time(
1388 time_t current, /* what is the initial time */
1389 enum tmt_en baseint, /* what is the basic interval */
1390 long basestep /* how many if these do we jump a time */
1396 localtime_r(¤t, &tm);
1401 tm. tm_sec += basestep;
1405 tm. tm_min += basestep;
1409 tm. tm_hour += basestep;
1413 tm. tm_mday += basestep;
1417 tm. tm_mday += 7 * basestep;
1421 tm. tm_mon += basestep;
1425 tm. tm_year += basestep;
1427 madetime = mktime(&tm);
1428 } while (madetime == -1); /* this is necessary to skip impssible times
1429 like the daylight saving time skips */
1435 /* calculate values required for PRINT and GPRINT functions */
1440 long i, ii, validsteps;
1443 int graphelement = 0;
1446 double magfact = -1;
1451 /* wow initializing tmvdef is quite a task :-) */
1452 time_t now = time(NULL);
1454 localtime_r(&now, &tmvdef);
1455 for (i = 0; i < im->gdes_c; i++) {
1456 vidx = im->gdes[i].vidx;
1457 switch (im->gdes[i].gf) {
1460 /* PRINT and GPRINT can now print VDEF generated values.
1461 * There's no need to do any calculations on them as these
1462 * calculations were already made.
1464 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1465 printval = im->gdes[vidx].vf.val;
1466 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1467 } else { /* need to calculate max,min,avg etcetera */
1468 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1469 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1472 for (ii = im->gdes[vidx].ds;
1473 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1474 if (!finite(im->gdes[vidx].data[ii]))
1476 if (isnan(printval)) {
1477 printval = im->gdes[vidx].data[ii];
1482 switch (im->gdes[i].cf) {
1486 case CF_DEVSEASONAL:
1490 printval += im->gdes[vidx].data[ii];
1493 printval = min(printval, im->gdes[vidx].data[ii]);
1497 printval = max(printval, im->gdes[vidx].data[ii]);
1500 printval = im->gdes[vidx].data[ii];
1503 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1504 if (validsteps > 1) {
1505 printval = (printval / validsteps);
1508 } /* prepare printval */
1510 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1511 /* Magfact is set to -1 upon entry to print_calc. If it
1512 * is still less than 0, then we need to run auto_scale.
1513 * Otherwise, put the value into the correct units. If
1514 * the value is 0, then do not set the symbol or magnification
1515 * so next the calculation will be performed again. */
1516 if (magfact < 0.0) {
1517 auto_scale(im, &printval, &si_symb, &magfact);
1518 if (printval == 0.0)
1521 printval /= magfact;
1523 *(++percent_s) = 's';
1524 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1525 auto_scale(im, &printval, &si_symb, &magfact);
1528 if (im->gdes[i].gf == GF_PRINT) {
1531 if (im->gdes[i].strftm) {
1532 prline.u_str = malloc((FMT_LEG_LEN + 2) * sizeof(char));
1533 strftime(prline.u_str,
1534 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1535 } else if (bad_format(im->gdes[i].format)) {
1537 ("bad format for PRINT in '%s'", im->gdes[i].format);
1541 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1545 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1550 if (im->gdes[i].strftm) {
1551 strftime(im->gdes[i].legend,
1552 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1554 if (bad_format(im->gdes[i].format)) {
1556 ("bad format for GPRINT in '%s'",
1557 im->gdes[i].format);
1560 #ifdef HAVE_SNPRINTF
1561 snprintf(im->gdes[i].legend,
1563 im->gdes[i].format, printval, si_symb);
1565 sprintf(im->gdes[i].legend,
1566 im->gdes[i].format, printval, si_symb);
1578 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1579 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1584 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1585 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1594 #ifdef WITH_PIECHART
1602 ("STACK should already be turned into LINE or AREA here");
1607 return graphelement;
1611 /* place legends with color spots */
1617 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1618 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1619 int fill = 0, fill_last;
1621 int leg_x = border, leg_y = im->yimg;
1622 int leg_y_prev = im->yimg;
1625 int i, ii, mark = 0;
1626 char prt_fctn; /*special printfunctions */
1627 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1630 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1631 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1632 rrd_set_error("malloc for legspace");
1636 if (im->extra_flags & FULL_SIZE_MODE)
1637 leg_y = leg_y_prev =
1638 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1639 for (i = 0; i < im->gdes_c; i++) {
1641 /* 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 <
1649 im->minval || im->gdes[i].yrule > im->maxval))
1650 im->gdes[i].legend[0] = '\0';
1651 if (im->gdes[i].gf == GF_VRULE
1652 && (im->gdes[i].xrule <
1653 im->start || im->gdes[i].xrule > im->end))
1654 im->gdes[i].legend[0] = '\0';
1657 leg_cc = strlen(im->gdes[i].legend);
1658 /* is there a controle code ant the end of the legend string ? */
1659 /* and it is not a tab \\t */
1661 && im->gdes[i].legend[leg_cc -
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') {
1679 ("Unknown control code at the end of '%s\\%c'",
1680 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);
1700 fill += legspace[i];
1703 gfx_get_text_width(im,
1713 im->tabwidth, im->gdes[i].legend);
1718 /* who said there was a special tag ... ? */
1719 if (prt_fctn == 'g') {
1723 if (prt_fctn == '\0') {
1724 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1725 /* just one legend item is left right or center */
1726 switch (default_txtalign) {
1741 /* is it time to place the legends ? */
1742 if (fill > im->ximg - 2 * border) {
1750 if (leg_c == 1 && prt_fctn == 'j') {
1756 if (prt_fctn != '\0') {
1758 if (leg_c >= 2 && prt_fctn == 'j') {
1759 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1763 if (prt_fctn == 'c')
1764 leg_x = (im->ximg - fill) / 2.0;
1765 if (prt_fctn == 'r')
1766 leg_x = im->ximg - fill - border;
1767 for (ii = mark; ii <= i; ii++) {
1768 if (im->gdes[ii].legend[0] == '\0')
1769 continue; /* skip empty legends */
1770 im->gdes[ii].leg_x = leg_x;
1771 im->gdes[ii].leg_y = leg_y;
1773 gfx_get_text_width(im, leg_x,
1782 im->tabwidth, im->gdes[ii].legend)
1787 if (im->extra_flags & FULL_SIZE_MODE) {
1788 /* only add y space if there was text on the line */
1789 if (leg_x > border || prt_fctn == 's')
1790 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1791 if (prt_fctn == 's')
1792 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1794 if (leg_x > border || prt_fctn == 's')
1795 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1796 if (prt_fctn == 's')
1797 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1805 if (im->extra_flags & FULL_SIZE_MODE) {
1806 if (leg_y != leg_y_prev) {
1807 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1809 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1812 im->yimg = leg_y_prev;
1813 /* if we did place some legends we have to add vertical space */
1814 if (leg_y != im->yimg)
1815 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1822 /* create a grid on the graph. it determines what to do
1823 from the values of xsize, start and end */
1825 /* the xaxis labels are determined from the number of seconds per pixel
1826 in the requested graph */
1830 int calc_horizontal_grid(
1838 int decimals, fractionals;
1840 im->ygrid_scale.labfact = 2;
1841 range = im->maxval - im->minval;
1842 scaledrange = range / im->magfact;
1843 /* does the scale of this graph make it impossible to put lines
1844 on it? If so, give up. */
1845 if (isnan(scaledrange)) {
1849 /* find grid spaceing */
1851 if (isnan(im->ygridstep)) {
1852 if (im->extra_flags & ALTYGRID) {
1853 /* find the value with max number of digits. Get number of digits */
1856 (max(fabs(im->maxval), fabs(im->minval)) *
1857 im->viewfactor / im->magfact));
1858 if (decimals <= 0) /* everything is small. make place for zero */
1860 im->ygrid_scale.gridstep =
1862 floor(log10(range * im->viewfactor / im->magfact))) /
1863 im->viewfactor * im->magfact;
1864 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1865 im->ygrid_scale.gridstep = 0.1;
1866 /* should have at least 5 lines but no more then 15 */
1867 if (range / im->ygrid_scale.gridstep < 5)
1868 im->ygrid_scale.gridstep /= 10;
1869 if (range / im->ygrid_scale.gridstep > 15)
1870 im->ygrid_scale.gridstep *= 10;
1871 if (range / im->ygrid_scale.gridstep > 5) {
1872 im->ygrid_scale.labfact = 1;
1873 if (range / im->ygrid_scale.gridstep > 8)
1874 im->ygrid_scale.labfact = 2;
1876 im->ygrid_scale.gridstep /= 5;
1877 im->ygrid_scale.labfact = 5;
1881 (im->ygrid_scale.gridstep *
1882 (double) im->ygrid_scale.labfact * im->viewfactor /
1884 if (fractionals < 0) { /* small amplitude. */
1885 int len = decimals - fractionals + 1;
1887 if (im->unitslength < len + 2)
1888 im->unitslength = len + 2;
1889 sprintf(im->ygrid_scale.labfmt,
1891 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1893 int len = decimals + 1;
1895 if (im->unitslength < len + 2)
1896 im->unitslength = len + 2;
1897 sprintf(im->ygrid_scale.labfmt,
1898 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1901 for (i = 0; ylab[i].grid > 0; i++) {
1902 pixel = im->ysize / (scaledrange / ylab[i].grid);
1908 for (i = 0; i < 4; i++) {
1909 if (pixel * ylab[gridind].lfac[i] >=
1910 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1911 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1916 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1919 im->ygrid_scale.gridstep = im->ygridstep;
1920 im->ygrid_scale.labfact = im->ylabfact;
1925 int draw_horizontal_grid(
1931 char graph_label[100];
1933 double X0 = im->xorigin;
1934 double X1 = im->xorigin + im->xsize;
1935 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1936 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1940 im->ygrid_scale.gridstep /
1941 (double) im->magfact * (double) im->viewfactor;
1942 MaxY = scaledstep * (double) egrid;
1943 for (i = sgrid; i <= egrid; i++) {
1945 im->ygrid_scale.gridstep * i);
1947 im->ygrid_scale.gridstep * (i + 1));
1949 if (floor(Y0 + 0.5) >=
1950 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1951 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1952 with the chosen settings. Add a label if required by settings, or if
1953 there is only one label so far and the next grid line is out of bounds. */
1954 if (i % im->ygrid_scale.labfact == 0
1956 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1957 if (im->symbol == ' ') {
1958 if (im->extra_flags & ALTYGRID) {
1959 sprintf(graph_label,
1960 im->ygrid_scale.labfmt,
1961 scaledstep * (double) i);
1964 sprintf(graph_label, "%4.1f",
1965 scaledstep * (double) i);
1967 sprintf(graph_label, "%4.0f",
1968 scaledstep * (double) i);
1972 char sisym = (i == 0 ? ' ' : im->symbol);
1974 if (im->extra_flags & ALTYGRID) {
1975 sprintf(graph_label,
1976 im->ygrid_scale.labfmt,
1977 scaledstep * (double) i, sisym);
1980 sprintf(graph_label, "%4.1f %c",
1981 scaledstep * (double) i, sisym);
1983 sprintf(graph_label, "%4.0f %c",
1984 scaledstep * (double) i, sisym);
1992 text_prop[TEXT_PROP_AXIS].
1994 im->graph_col[GRC_FONT],
1996 text_prop[TEXT_PROP_AXIS].
1999 text_prop[TEXT_PROP_AXIS].
2000 size, im->tabwidth, 0.0,
2001 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2002 gfx_line(im, X0 - 2, Y0, X0, Y0,
2003 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2004 gfx_line(im, X1, Y0, X1 + 2, Y0,
2005 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2006 gfx_dashed_line(im, X0 - 2, Y0,
2012 im->grid_dash_on, im->grid_dash_off);
2013 } else if (!(im->extra_flags & NOMINOR)) {
2016 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2017 gfx_line(im, X1, Y0, X1 + 2, Y0,
2018 GRIDWIDTH, im->graph_col[GRC_GRID]);
2019 gfx_dashed_line(im, X0 - 1, Y0,
2023 graph_col[GRC_GRID],
2024 im->grid_dash_on, im->grid_dash_off);
2031 /* this is frexp for base 10 */
2042 iexp = floor(log(fabs(x)) / log(10));
2043 mnt = x / pow(10.0, iexp);
2046 mnt = x / pow(10.0, iexp);
2052 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
2053 /* yes we are loosing precision by doing tos with floats instead of doubles
2054 but it seems more stable this way. */
2057 /* logaritmic horizontal grid */
2058 int horizontal_log_grid(
2062 double yloglab[][10] = {
2064 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2066 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2068 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2085 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2087 int i, j, val_exp, min_exp;
2088 double nex; /* number of decades in data */
2089 double logscale; /* scale in logarithmic space */
2090 int exfrac = 1; /* decade spacing */
2091 int mid = -1; /* row in yloglab for major grid */
2092 double mspac; /* smallest major grid spacing (pixels) */
2093 int flab; /* first value in yloglab to use */
2094 double value, tmp, pre_value;
2096 char graph_label[100];
2098 nex = log10(im->maxval / im->minval);
2099 logscale = im->ysize / nex;
2100 /* major spacing for data with high dynamic range */
2101 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2108 /* major spacing for less dynamic data */
2110 /* search best row in yloglab */
2112 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2113 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2116 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2119 /* find first value in yloglab */
2121 yloglab[mid][flab] < 10
2122 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2123 if (yloglab[mid][flab] == 10.0) {
2128 if (val_exp % exfrac)
2129 val_exp += abs(-val_exp % exfrac);
2131 X1 = im->xorigin + im->xsize;
2136 value = yloglab[mid][flab] * pow(10.0, val_exp);
2137 if (AlmostEqual2sComplement(value, pre_value, 4))
2138 break; /* it seems we are not converging */
2140 Y0 = ytr(im, value);
2141 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2143 /* major grid line */
2145 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2146 gfx_line(im, X1, Y0, X1 + 2, Y0,
2147 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2148 gfx_dashed_line(im, X0 - 2, Y0,
2153 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2155 if (im->extra_flags & FORCE_UNITS_SI) {
2160 scale = floor(val_exp / 3.0);
2162 pvalue = pow(10.0, val_exp % 3);
2164 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2165 pvalue *= yloglab[mid][flab];
2166 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2167 && ((scale + si_symbcenter) >= 0))
2168 symbol = si_symbol[scale + si_symbcenter];
2171 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2173 sprintf(graph_label, "%3.0e", value);
2177 text_prop[TEXT_PROP_AXIS].
2179 im->graph_col[GRC_FONT],
2181 text_prop[TEXT_PROP_AXIS].
2184 text_prop[TEXT_PROP_AXIS].
2185 size, im->tabwidth, 0.0,
2186 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2188 if (mid < 4 && exfrac == 1) {
2189 /* find first and last minor line behind current major line
2190 * i is the first line and j tha last */
2192 min_exp = val_exp - 1;
2193 for (i = 1; yloglab[mid][i] < 10.0; i++);
2194 i = yloglab[mid][i - 1] + 1;
2198 i = yloglab[mid][flab - 1] + 1;
2199 j = yloglab[mid][flab];
2202 /* draw minor lines below current major line */
2203 for (; i < j; i++) {
2205 value = i * pow(10.0, min_exp);
2206 if (value < im->minval)
2208 Y0 = ytr(im, value);
2209 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2214 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2215 gfx_line(im, X1, Y0, X1 + 2, Y0,
2216 GRIDWIDTH, im->graph_col[GRC_GRID]);
2217 gfx_dashed_line(im, X0 - 1, Y0,
2221 graph_col[GRC_GRID],
2222 im->grid_dash_on, im->grid_dash_off);
2224 } else if (exfrac > 1) {
2225 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2226 value = pow(10.0, i);
2227 if (value < im->minval)
2229 Y0 = ytr(im, value);
2230 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2235 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2236 gfx_line(im, X1, Y0, X1 + 2, Y0,
2237 GRIDWIDTH, im->graph_col[GRC_GRID]);
2238 gfx_dashed_line(im, X0 - 1, Y0,
2242 graph_col[GRC_GRID],
2243 im->grid_dash_on, im->grid_dash_off);
2248 if (yloglab[mid][++flab] == 10.0) {
2254 /* draw minor lines after highest major line */
2255 if (mid < 4 && exfrac == 1) {
2256 /* find first and last minor line below current major line
2257 * i is the first line and j tha last */
2259 min_exp = val_exp - 1;
2260 for (i = 1; yloglab[mid][i] < 10.0; i++);
2261 i = yloglab[mid][i - 1] + 1;
2265 i = yloglab[mid][flab - 1] + 1;
2266 j = yloglab[mid][flab];
2269 /* draw minor lines below current major line */
2270 for (; i < j; i++) {
2272 value = i * pow(10.0, min_exp);
2273 if (value < im->minval)
2275 Y0 = ytr(im, value);
2276 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2280 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2281 gfx_line(im, X1, Y0, X1 + 2, Y0,
2282 GRIDWIDTH, im->graph_col[GRC_GRID]);
2283 gfx_dashed_line(im, X0 - 1, Y0,
2287 graph_col[GRC_GRID],
2288 im->grid_dash_on, im->grid_dash_off);
2291 /* fancy minor gridlines */
2292 else if (exfrac > 1) {
2293 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2294 value = pow(10.0, i);
2295 if (value < im->minval)
2297 Y0 = ytr(im, value);
2298 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2302 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2303 gfx_line(im, X1, Y0, X1 + 2, Y0,
2304 GRIDWIDTH, im->graph_col[GRC_GRID]);
2305 gfx_dashed_line(im, X0 - 1, Y0,
2309 graph_col[GRC_GRID],
2310 im->grid_dash_on, im->grid_dash_off);
2321 int xlab_sel; /* which sort of label and grid ? */
2322 time_t ti, tilab, timajor;
2324 char graph_label[100];
2325 double X0, Y0, Y1; /* points for filled graph and more */
2328 /* the type of time grid is determined by finding
2329 the number of seconds per pixel in the graph */
2330 if (im->xlab_user.minsec == -1) {
2331 factor = (im->end - im->start) / im->xsize;
2333 while (xlab[xlab_sel + 1].minsec !=
2334 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2336 } /* pick the last one */
2337 while (xlab[xlab_sel - 1].minsec ==
2338 xlab[xlab_sel].minsec
2339 && xlab[xlab_sel].length > (im->end - im->start)) {
2341 } /* go back to the smallest size */
2342 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2343 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2344 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2345 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2346 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2347 im->xlab_user.labst = xlab[xlab_sel].labst;
2348 im->xlab_user.precis = xlab[xlab_sel].precis;
2349 im->xlab_user.stst = xlab[xlab_sel].stst;
2352 /* y coords are the same for every line ... */
2354 Y1 = im->yorigin - im->ysize;
2355 /* paint the minor grid */
2356 if (!(im->extra_flags & NOMINOR)) {
2357 for (ti = find_first_time(im->start,
2365 find_first_time(im->start,
2372 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2374 /* are we inside the graph ? */
2375 if (ti < im->start || ti > im->end)
2377 while (timajor < ti) {
2378 timajor = find_next_time(timajor,
2381 mgridtm, im->xlab_user.mgridst);
2384 continue; /* skip as falls on major grid line */
2386 gfx_line(im, X0, Y1 - 2, X0, Y1,
2387 GRIDWIDTH, im->graph_col[GRC_GRID]);
2388 gfx_line(im, X0, Y0, X0, Y0 + 2,
2389 GRIDWIDTH, im->graph_col[GRC_GRID]);
2390 gfx_dashed_line(im, X0, Y0 + 1, X0,
2393 graph_col[GRC_GRID],
2394 im->grid_dash_on, im->grid_dash_off);
2398 /* paint the major grid */
2399 for (ti = find_first_time(im->start,
2407 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2409 /* are we inside the graph ? */
2410 if (ti < im->start || ti > im->end)
2413 gfx_line(im, X0, Y1 - 2, X0, Y1,
2414 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2415 gfx_line(im, X0, Y0, X0, Y0 + 3,
2416 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2417 gfx_dashed_line(im, X0, Y0 + 3, X0,
2421 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2423 /* paint the labels below the graph */
2425 find_first_time(im->start -
2434 im->xlab_user.precis / 2;
2435 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2437 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2438 /* are we inside the graph ? */
2439 if (tilab < im->start || tilab > im->end)
2442 localtime_r(&tilab, &tm);
2443 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2445 # error "your libc has no strftime I guess we'll abort the exercise here."
2450 im->graph_col[GRC_FONT],
2452 text_prop[TEXT_PROP_AXIS].
2455 text_prop[TEXT_PROP_AXIS].
2456 size, im->tabwidth, 0.0,
2457 GFX_H_CENTER, GFX_V_TOP, graph_label);
2466 /* draw x and y axis */
2467 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2468 im->xorigin+im->xsize,im->yorigin-im->ysize,
2469 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2471 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2472 im->xorigin+im->xsize,im->yorigin-im->ysize,
2473 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2475 gfx_line(im, im->xorigin - 4,
2477 im->xorigin + im->xsize +
2478 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2479 gfx_line(im, im->xorigin,
2482 im->yorigin - im->ysize -
2483 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2484 /* arrow for X and Y axis direction */
2485 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 */
2486 im->graph_col[GRC_ARROW]);
2488 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 */
2489 im->graph_col[GRC_ARROW]);
2498 double X0, Y0; /* points for filled graph and more */
2499 struct gfx_color_t water_color;
2501 /* draw 3d border */
2502 gfx_new_area(im, 0, im->yimg,
2503 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2504 gfx_add_point(im, im->ximg - 2, 2);
2505 gfx_add_point(im, im->ximg, 0);
2506 gfx_add_point(im, 0, 0);
2508 gfx_new_area(im, 2, im->yimg - 2,
2510 im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2511 gfx_add_point(im, im->ximg, 0);
2512 gfx_add_point(im, im->ximg, im->yimg);
2513 gfx_add_point(im, 0, im->yimg);
2515 if (im->draw_x_grid == 1)
2517 if (im->draw_y_grid == 1) {
2518 if (im->logarithmic) {
2519 res = horizontal_log_grid(im);
2521 res = draw_horizontal_grid(im);
2524 /* dont draw horizontal grid if there is no min and max val */
2526 char *nodata = "No Data found";
2528 gfx_text(im, im->ximg / 2,
2531 im->graph_col[GRC_FONT],
2533 text_prop[TEXT_PROP_AXIS].
2536 text_prop[TEXT_PROP_AXIS].
2537 size, im->tabwidth, 0.0,
2538 GFX_H_CENTER, GFX_V_CENTER, nodata);
2542 /* yaxis unit description */
2547 im->graph_col[GRC_FONT],
2549 text_prop[TEXT_PROP_UNIT].
2552 text_prop[TEXT_PROP_UNIT].
2554 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2558 im->graph_col[GRC_FONT],
2560 text_prop[TEXT_PROP_TITLE].
2563 text_prop[TEXT_PROP_TITLE].
2564 size, im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2565 /* rrdtool 'logo' */
2566 water_color = im->graph_col[GRC_FONT];
2567 water_color.alpha = 0.3;
2568 gfx_text(im, im->ximg - 4, 5,
2571 text_prop[TEXT_PROP_AXIS].
2572 font, 5.5, im->tabwidth,
2573 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2574 /* graph watermark */
2575 if (im->watermark[0] != '\0') {
2577 im->ximg / 2, im->yimg - 6,
2580 text_prop[TEXT_PROP_AXIS].
2581 font, 5.5, im->tabwidth, 0,
2582 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2586 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2587 for (i = 0; i < im->gdes_c; i++) {
2588 if (im->gdes[i].legend[0] == '\0')
2590 /* im->gdes[i].leg_y is the bottom of the legend */
2591 X0 = im->gdes[i].leg_x;
2592 Y0 = im->gdes[i].leg_y;
2593 gfx_text(im, X0, Y0,
2594 im->graph_col[GRC_FONT],
2597 [TEXT_PROP_LEGEND].font,
2600 [TEXT_PROP_LEGEND].size,
2602 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2603 /* The legend for GRAPH items starts with "M " to have
2604 enough space for the box */
2605 if (im->gdes[i].gf != GF_PRINT &&
2606 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2610 boxH = gfx_get_text_width(im, 0,
2618 size, im->tabwidth, "o") * 1.2;
2620 /* shift the box up a bit */
2622 /* make sure transparent colors show up the same way as in the graph */
2625 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2626 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2628 gfx_new_area(im, X0, Y0 - boxV, X0,
2629 Y0, X0 + boxH, Y0, im->gdes[i].col);
2630 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2633 cairo_new_path(im->cr);
2634 cairo_set_line_width(im->cr, 1.0);
2637 gfx_line_fit(im, &X0, &Y0);
2638 gfx_line_fit(im, &X1, &Y1);
2639 cairo_move_to(im->cr, X0, Y0);
2640 cairo_line_to(im->cr, X1, Y0);
2641 cairo_line_to(im->cr, X1, Y1);
2642 cairo_line_to(im->cr, X0, Y1);
2643 cairo_close_path(im->cr);
2644 cairo_set_source_rgba(im->cr,
2656 blue, im->graph_col[GRC_FRAME].alpha);
2657 if (im->gdes[i].dash) {
2658 /* make box borders in legend dashed if the graph is dashed */
2662 cairo_set_dash(im->cr, dashes, 1, 0.0);
2664 cairo_stroke(im->cr);
2665 cairo_restore(im->cr);
2672 /*****************************************************
2673 * lazy check make sure we rely need to create this graph
2674 *****************************************************/
2681 struct stat imgstat;
2684 return 0; /* no lazy option */
2685 if (strlen(im->graphfile) == 0)
2686 return 0; /* inmemory option */
2687 if (stat(im->graphfile, &imgstat) != 0)
2688 return 0; /* can't stat */
2689 /* one pixel in the existing graph is more then what we would
2691 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2693 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2694 return 0; /* the file does not exist */
2695 switch (im->imgformat) {
2697 size = PngSize(fd, &(im->ximg), &(im->yimg));
2707 int graph_size_location(
2712 /* The actual size of the image to draw is determined from
2713 ** several sources. The size given on the command line is
2714 ** the graph area but we need more as we have to draw labels
2715 ** and other things outside the graph area
2718 int Xvertical = 0, Ytitle =
2719 0, Xylabel = 0, Xmain = 0, Ymain =
2720 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2722 if (im->extra_flags & ONLY_GRAPH) {
2724 im->ximg = im->xsize;
2725 im->yimg = im->ysize;
2726 im->yorigin = im->ysize;
2731 /** +---+--------------------------------------------+
2732 ** | y |...............graph title..................|
2733 ** | +---+-------------------------------+--------+
2736 ** | i | a | | pie |
2737 ** | s | x | main graph area | chart |
2742 ** | l | b +-------------------------------+--------+
2743 ** | e | l | x axis labels | |
2744 ** +---+---+-------------------------------+--------+
2745 ** |....................legends.....................|
2746 ** +------------------------------------------------+
2748 ** +------------------------------------------------+
2751 if (im->ylegend[0] != '\0') {
2752 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2755 if (im->title[0] != '\0') {
2756 /* The title is placed "inbetween" two text lines so it
2757 ** automatically has some vertical spacing. The horizontal
2758 ** spacing is added here, on each side.
2760 /* if necessary, reduce the font size of the title until it fits the image width */
2761 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2765 if (im->draw_x_grid) {
2766 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2768 if (im->draw_y_grid || im->forceleftspace) {
2770 gfx_get_text_width(im, 0,
2778 size, im->tabwidth, "0") * im->unitslength;
2782 if (im->extra_flags & FULL_SIZE_MODE) {
2783 /* The actual size of the image to draw has been determined by the user.
2784 ** The graph area is the space remaining after accounting for the legend,
2785 ** the watermark, the pie chart, the axis labels, and the title.
2788 im->ximg = im->xsize;
2789 im->yimg = im->ysize;
2790 im->yorigin = im->ysize;
2793 im->yorigin += Ytitle;
2794 /* Now calculate the total size. Insert some spacing where
2795 desired. im->xorigin and im->yorigin need to correspond
2796 with the lower left corner of the main graph area or, if
2797 this one is not set, the imaginary box surrounding the
2799 /* Initial size calculation for the main graph area */
2800 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2802 Xmain -= Xspacing; /* put space between main graph area and right edge */
2803 im->xorigin = Xspacing + Xylabel;
2804 /* the length of the title should not influence with width of the graph
2805 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2806 if (Xvertical) { /* unit description */
2808 im->xorigin += Xvertical;
2812 /* The vertical size of the image is known in advance. The main graph area
2813 ** (Ymain) and im->yorigin must be set according to the space requirements
2814 ** of the legend and the axis labels.
2816 if (im->extra_flags & NOLEGEND) {
2817 /* set dimensions correctly if using full size mode with no legend */
2820 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2821 Ymain = im->yorigin;
2823 /* Determine where to place the legends onto the image.
2824 ** Set Ymain and adjust im->yorigin to match the space requirements.
2826 if (leg_place(im, &Ymain) == -1)
2831 /* remove title space *or* some padding above the graph from the main graph area */
2835 Ymain -= 1.5 * Yspacing;
2838 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2839 if (im->watermark[0] != '\0') {
2840 Ymain -= Ywatermark;
2844 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2846 /* The actual size of the image to draw is determined from
2847 ** several sources. The size given on the command line is
2848 ** the graph area but we need more as we have to draw labels
2849 ** and other things outside the graph area.
2852 if (im->ylegend[0] != '\0') {
2853 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2857 if (im->title[0] != '\0') {
2858 /* The title is placed "inbetween" two text lines so it
2859 ** automatically has some vertical spacing. The horizontal
2860 ** spacing is added here, on each side.
2862 /* don't care for the with of the title
2863 Xtitle = gfx_get_text_width(im->canvas, 0,
2864 im->text_prop[TEXT_PROP_TITLE].font,
2865 im->text_prop[TEXT_PROP_TITLE].size,
2867 im->title, 0) + 2*Xspacing; */
2868 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2875 /* Now calculate the total size. Insert some spacing where
2876 desired. im->xorigin and im->yorigin need to correspond
2877 with the lower left corner of the main graph area or, if
2878 this one is not set, the imaginary box surrounding the
2881 /* The legend width cannot yet be determined, as a result we
2882 ** have problems adjusting the image to it. For now, we just
2883 ** forget about it at all; the legend will have to fit in the
2884 ** size already allocated.
2886 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2888 im->ximg += Xspacing;
2889 im->xorigin = Xspacing + Xylabel;
2890 /* the length of the title should not influence with width of the graph
2891 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2892 if (Xvertical) { /* unit description */
2893 im->ximg += Xvertical;
2894 im->xorigin += Xvertical;
2897 /* The vertical size is interesting... we need to compare
2898 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2899 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2900 ** in order to start even thinking about Ylegend or Ywatermark.
2902 ** Do it in three portions: First calculate the inner part,
2903 ** then do the legend, then adjust the total height of the img,
2904 ** adding space for a watermark if one exists;
2906 /* reserve space for main and/or pie */
2907 im->yimg = Ymain + Yxlabel;
2908 im->yorigin = im->yimg - Yxlabel;
2909 /* reserve space for the title *or* some padding above the graph */
2912 im->yorigin += Ytitle;
2914 im->yimg += 1.5 * Yspacing;
2915 im->yorigin += 1.5 * Yspacing;
2917 /* reserve space for padding below the graph */
2918 im->yimg += Yspacing;
2919 /* Determine where to place the legends onto the image.
2920 ** Adjust im->yimg to match the space requirements.
2922 if (leg_place(im, 0) == -1)
2924 if (im->watermark[0] != '\0') {
2925 im->yimg += Ywatermark;
2933 static cairo_status_t cairo_output(
2937 unsigned int length)
2939 image_desc_t *im = closure;
2941 im->rendered_image =
2942 realloc(im->rendered_image, im->rendered_image_size + length);
2943 if (im->rendered_image == NULL)
2944 return CAIRO_STATUS_WRITE_ERROR;
2945 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2946 im->rendered_image_size += length;
2947 return CAIRO_STATUS_SUCCESS;
2950 /* draw that picture thing ... */
2955 int lazy = lazy_check(im);
2956 double areazero = 0.0;
2957 graph_desc_t *lastgdes = NULL;
2959 PangoFontMap *font_map = pango_cairo_font_map_get_default();
2961 /* if we are lazy and there is nothing to PRINT ... quit now */
2962 if (lazy && im->prt_c == 0)
2964 /* pull the data from the rrd files ... */
2965 if (data_fetch(im) == -1)
2967 /* evaluate VDEF and CDEF operations ... */
2968 if (data_calc(im) == -1)
2970 /* calculate and PRINT and GPRINT definitions. We have to do it at
2971 * this point because it will affect the length of the legends
2972 * if there are no graph elements we stop here ...
2973 * if we are lazy, try to quit ...
2978 if ((i == 0) || lazy)
2980 /**************************************************************
2981 *** Calculating sizes and locations became a bit confusing ***
2982 *** so I moved this into a separate function. ***
2983 **************************************************************/
2984 if (graph_size_location(im, i) == -1)
2987 info.u_cnt = im->xorigin;
2988 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
2989 info.u_cnt = im->yorigin - im->ysize;
2990 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
2991 info.u_cnt = im->xsize;
2992 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
2993 info.u_cnt = im->ysize;
2994 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
2995 info.u_cnt = im->ximg;
2996 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2997 info.u_cnt = im->yimg;
2998 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
3000 /* get actual drawing data and find min and max values */
3001 if (data_proc(im) == -1)
3003 if (!im->logarithmic) {
3007 /* identify si magnitude Kilo, Mega Giga ? */
3008 if (!im->rigid && !im->logarithmic)
3009 expand_range(im); /* make sure the upper and lower limit are
3012 info.u_val = im->minval;
3013 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3014 info.u_val = im->maxval;
3015 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3017 if (!calc_horizontal_grid(im))
3022 apply_gridfit(im); */
3023 /* the actual graph is created by going through the individual
3024 graph elements and then drawing them */
3025 cairo_surface_destroy(im->surface);
3026 switch (im->imgformat) {
3029 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3030 im->ximg * im->zoom,
3031 im->yimg * im->zoom);
3035 im->surface = strlen(im->graphfile)
3037 cairo_pdf_surface_create_for_stream
3038 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom)
3039 : cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3040 im->yimg * im->zoom);
3044 im->surface = strlen(im->graphfile)
3046 cairo_ps_surface_create_for_stream
3047 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom)
3048 : cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3049 im->yimg * im->zoom);
3053 im->surface = strlen(im->graphfile)
3055 cairo_svg_surface_create_for_stream
3056 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom)
3057 : cairo_svg_surface_create(im->
3060 ximg * im->zoom, im->yimg * im->zoom);
3061 cairo_svg_surface_restrict_to_version
3062 (im->surface, CAIRO_SVG_VERSION_1_1);
3065 im->cr = cairo_create(im->surface);
3066 cairo_set_antialias(im->cr, im->graph_antialias);
3067 cairo_scale(im->cr, im->zoom, im->zoom);
3068 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3069 gfx_new_area(im, 0, 0, 0, im->yimg,
3070 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3071 gfx_add_point(im, im->ximg, 0);
3073 gfx_new_area(im, im->xorigin,
3076 im->xsize, im->yorigin,
3079 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3080 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3082 if (im->minval > 0.0)
3083 areazero = im->minval;
3084 if (im->maxval < 0.0)
3085 areazero = im->maxval;
3086 for (i = 0; i < im->gdes_c; i++) {
3087 switch (im->gdes[i].gf) {
3101 for (ii = 0; ii < im->xsize; ii++) {
3102 if (!isnan(im->gdes[i].p_data[ii])
3103 && im->gdes[i].p_data[ii] != 0.0) {
3104 if (im->gdes[i].yrule > 0) {
3111 im->ysize, 1.0, im->gdes[i].col);
3112 } else if (im->gdes[i].yrule < 0) {
3115 im->yorigin - im->ysize,
3120 im->ysize, 1.0, im->gdes[i].col);
3127 /* fix data points at oo and -oo */
3128 for (ii = 0; ii < im->xsize; ii++) {
3129 if (isinf(im->gdes[i].p_data[ii])) {
3130 if (im->gdes[i].p_data[ii] > 0) {
3131 im->gdes[i].p_data[ii] = im->maxval;
3133 im->gdes[i].p_data[ii] = im->minval;
3139 /* *******************************************************
3144 -------|--t-1--t--------------------------------
3146 if we know the value at time t was a then
3147 we draw a square from t-1 to t with the value a.
3149 ********************************************************* */
3150 if (im->gdes[i].col.alpha != 0.0) {
3151 /* GF_LINE and friend */
3152 if (im->gdes[i].gf == GF_LINE) {
3153 double last_y = 0.0;
3157 cairo_new_path(im->cr);
3158 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3159 if (im->gdes[i].dash) {
3160 cairo_set_dash(im->cr,
3161 im->gdes[i].p_dashes,
3162 im->gdes[i].ndash, im->gdes[i].offset);
3165 for (ii = 1; ii < im->xsize; ii++) {
3166 if (isnan(im->gdes[i].p_data[ii])
3167 || (im->slopemode == 1
3168 && isnan(im->gdes[i].p_data[ii - 1]))) {
3173 last_y = ytr(im, im->gdes[i].p_data[ii]);
3174 if (im->slopemode == 0) {
3175 double x = ii - 1 + im->xorigin;
3178 gfx_line_fit(im, &x, &y);
3179 cairo_move_to(im->cr, x, y);
3180 x = ii + im->xorigin;
3182 gfx_line_fit(im, &x, &y);
3183 cairo_line_to(im->cr, x, y);
3185 double x = ii - 1 + im->xorigin;
3187 ytr(im, im->gdes[i].p_data[ii - 1]);
3188 gfx_line_fit(im, &x, &y);
3189 cairo_move_to(im->cr, x, y);
3190 x = ii + im->xorigin;
3192 gfx_line_fit(im, &x, &y);
3193 cairo_line_to(im->cr, x, y);
3197 double x1 = ii + im->xorigin;
3198 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3200 if (im->slopemode == 0
3201 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3202 double x = ii - 1 + im->xorigin;
3205 gfx_line_fit(im, &x, &y);
3206 cairo_line_to(im->cr, x, y);
3209 gfx_line_fit(im, &x1, &y1);
3210 cairo_line_to(im->cr, x1, y1);
3213 cairo_set_source_rgba(im->cr,
3219 col.blue, im->gdes[i].col.alpha);
3220 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3221 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3222 cairo_stroke(im->cr);
3223 cairo_restore(im->cr);
3227 (double *) malloc(sizeof(double) * im->xsize * 2);
3229 (double *) malloc(sizeof(double) * im->xsize * 2);
3231 (double *) malloc(sizeof(double) * im->xsize * 2);
3233 (double *) malloc(sizeof(double) * im->xsize * 2);
3236 for (ii = 0; ii <= im->xsize; ii++) {
3239 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3245 AlmostEqual2sComplement(foreY
3249 AlmostEqual2sComplement(foreY
3259 foreY[cntI], im->gdes[i].col);
3260 while (cntI < idxI) {
3265 AlmostEqual2sComplement(foreY
3269 AlmostEqual2sComplement(foreY
3276 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3278 gfx_add_point(im, backX[idxI], backY[idxI]);
3284 AlmostEqual2sComplement(backY
3288 AlmostEqual2sComplement(backY
3295 gfx_add_point(im, backX[idxI], backY[idxI]);
3305 if (ii == im->xsize)
3307 if (im->slopemode == 0 && ii == 0) {
3310 if (isnan(im->gdes[i].p_data[ii])) {
3314 ytop = ytr(im, im->gdes[i].p_data[ii]);
3315 if (lastgdes && im->gdes[i].stack) {
3316 ybase = ytr(im, lastgdes->p_data[ii]);
3318 ybase = ytr(im, areazero);
3320 if (ybase == ytop) {
3326 double extra = ytop;
3331 if (im->slopemode == 0) {
3332 backY[++idxI] = ybase - 0.2;
3333 backX[idxI] = ii + im->xorigin - 1;
3334 foreY[idxI] = ytop + 0.2;
3335 foreX[idxI] = ii + im->xorigin - 1;
3337 backY[++idxI] = ybase - 0.2;
3338 backX[idxI] = ii + im->xorigin;
3339 foreY[idxI] = ytop + 0.2;
3340 foreX[idxI] = ii + im->xorigin;
3342 /* close up any remaining area */
3347 } /* else GF_LINE */
3349 /* if color != 0x0 */
3350 /* make sure we do not run into trouble when stacking on NaN */
3351 for (ii = 0; ii < im->xsize; ii++) {
3352 if (isnan(im->gdes[i].p_data[ii])) {
3353 if (lastgdes && (im->gdes[i].stack)) {
3354 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3356 im->gdes[i].p_data[ii] = areazero;
3360 lastgdes = &(im->gdes[i]);
3364 ("STACK should already be turned into LINE or AREA here");
3370 /* grid_paint also does the text */
3371 if (!(im->extra_flags & ONLY_GRAPH))
3373 if (!(im->extra_flags & ONLY_GRAPH))
3375 /* the RULES are the last thing to paint ... */
3376 for (i = 0; i < im->gdes_c; i++) {
3378 switch (im->gdes[i].gf) {
3380 if (im->gdes[i].yrule >= im->minval
3381 && im->gdes[i].yrule <= im->maxval) {
3383 if (im->gdes[i].dash) {
3384 cairo_set_dash(im->cr,
3385 im->gdes[i].p_dashes,
3386 im->gdes[i].ndash, im->gdes[i].offset);
3388 gfx_line(im, im->xorigin,
3389 ytr(im, im->gdes[i].yrule),
3390 im->xorigin + im->xsize,
3391 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3392 cairo_stroke(im->cr);
3393 cairo_restore(im->cr);
3397 if (im->gdes[i].xrule >= im->start
3398 && im->gdes[i].xrule <= im->end) {
3400 if (im->gdes[i].dash) {
3401 cairo_set_dash(im->cr,
3402 im->gdes[i].p_dashes,
3403 im->gdes[i].ndash, im->gdes[i].offset);
3406 xtr(im, im->gdes[i].xrule),
3407 im->yorigin, xtr(im,
3411 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3412 cairo_stroke(im->cr);
3413 cairo_restore(im->cr);
3422 switch (im->imgformat) {
3425 cairo_status_t status;
3427 status = strlen(im->graphfile) ?
3428 cairo_surface_write_to_png(im->surface, im->graphfile)
3429 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3432 if (status != CAIRO_STATUS_SUCCESS) {
3433 rrd_set_error("Could not save png to '%s'", im->graphfile);
3439 if (strlen(im->graphfile)) {
3440 cairo_show_page(im->cr);
3442 cairo_surface_finish(im->surface);
3451 /*****************************************************
3453 *****************************************************/
3460 if ((im->gdes = (graph_desc_t *)
3461 rrd_realloc(im->gdes, (im->gdes_c)
3462 * sizeof(graph_desc_t))) == NULL) {
3463 rrd_set_error("realloc graph_descs");
3468 im->gdes[im->gdes_c - 1].step = im->step;
3469 im->gdes[im->gdes_c - 1].step_orig = im->step;
3470 im->gdes[im->gdes_c - 1].stack = 0;
3471 im->gdes[im->gdes_c - 1].linewidth = 0;
3472 im->gdes[im->gdes_c - 1].debug = 0;
3473 im->gdes[im->gdes_c - 1].start = im->start;
3474 im->gdes[im->gdes_c - 1].start_orig = im->start;
3475 im->gdes[im->gdes_c - 1].end = im->end;
3476 im->gdes[im->gdes_c - 1].end_orig = im->end;
3477 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3478 im->gdes[im->gdes_c - 1].data = NULL;
3479 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3480 im->gdes[im->gdes_c - 1].data_first = 0;
3481 im->gdes[im->gdes_c - 1].p_data = NULL;
3482 im->gdes[im->gdes_c - 1].rpnp = NULL;
3483 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3484 im->gdes[im->gdes_c - 1].shift = 0.0;
3485 im->gdes[im->gdes_c - 1].dash = 0;
3486 im->gdes[im->gdes_c - 1].ndash = 0;
3487 im->gdes[im->gdes_c - 1].offset = 0;
3488 im->gdes[im->gdes_c - 1].col.red = 0.0;
3489 im->gdes[im->gdes_c - 1].col.green = 0.0;
3490 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3491 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3492 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3493 im->gdes[im->gdes_c - 1].format[0] = '\0';
3494 im->gdes[im->gdes_c - 1].strftm = 0;
3495 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3496 im->gdes[im->gdes_c - 1].ds = -1;
3497 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3498 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3499 im->gdes[im->gdes_c - 1].yrule = DNAN;
3500 im->gdes[im->gdes_c - 1].xrule = 0;
3504 /* copies input untill the first unescaped colon is found
3505 or until input ends. backslashes have to be escaped as well */
3507 const char *const input,
3513 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3514 if (input[inp] == '\\'
3515 && input[inp + 1] != '\0'
3516 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3517 output[outp++] = input[++inp];
3519 output[outp++] = input[inp];
3522 output[outp] = '\0';
3526 /* Now just a wrapper around rrd_graph_v */
3538 info_t *grinfo = NULL;
3541 grinfo = rrd_graph_v(argc, argv);
3547 if (strcmp(walker->key, "image_info") == 0) {
3550 rrd_realloc((*prdata),
3551 (prlines + 1) * sizeof(char *))) == NULL) {
3552 rrd_set_error("realloc prdata");
3555 /* imginfo goes to position 0 in the prdata array */
3556 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3557 + 2) * sizeof(char));
3558 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3559 (*prdata)[prlines] = NULL;
3561 /* skip anything else */
3562 walker = walker->next;
3566 if (strcmp(walker->key, "image_width") == 0) {
3567 *xsize = walker->value.u_int;
3568 } else if (strcmp(walker->key, "image_height") == 0) {
3569 *ysize = walker->value.u_int;
3570 } else if (strcmp(walker->key, "value_min") == 0) {
3571 *ymin = walker->value.u_val;
3572 } else if (strcmp(walker->key, "value_max") == 0) {
3573 *ymax = walker->value.u_val;
3574 } else if (strncmp(walker->key, "print", 6) == 0) { /* keys are prdate[0..] */
3577 rrd_realloc((*prdata),
3578 (prlines + 1) * sizeof(char *))) == NULL) {
3579 rrd_set_error("realloc prdata");
3582 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3583 + 2) * sizeof(char));
3584 (*prdata)[prlines] = NULL;
3585 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3586 } else if (strcmp(walker->key, "image") == 0) {
3587 fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3588 (stream ? stream : stdout));
3590 /* skip anything else */
3591 walker = walker->next;
3598 /* Some surgery done on this function, it became ridiculously big.
3600 ** - initializing now in rrd_graph_init()
3601 ** - options parsing now in rrd_graph_options()
3602 ** - script parsing now in rrd_graph_script()
3604 info_t *rrd_graph_v(
3611 rrd_graph_init(&im);
3612 /* a dummy surface so that we can measure text sizes for placements */
3613 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3614 im.cr = cairo_create(im.surface);
3615 rrd_graph_options(argc, argv, &im);
3616 if (rrd_test_error()) {
3617 info_free(im.grinfo);
3622 if (optind >= argc) {
3623 info_free(im.grinfo);
3625 rrd_set_error("missing filename");
3629 if (strlen(argv[optind]) >= MAXPATH) {
3630 rrd_set_error("filename (including path) too long");
3631 info_free(im.grinfo);
3636 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3637 im.graphfile[MAXPATH - 1] = '\0';
3639 if (strcmp(im.graphfile, "-") == 0) {
3640 im.graphfile[0] = '\0';
3643 rrd_graph_script(argc, argv, &im, 1);
3644 if (rrd_test_error()) {
3645 info_free(im.grinfo);
3650 /* Everything is now read and the actual work can start */
3652 if (graph_paint(&im) == -1) {
3653 info_free(im.grinfo);
3659 /* The image is generated and needs to be output.
3660 ** Also, if needed, print a line with information about the image.
3667 sprintf_alloc(im.imginfo,
3670 im.ximg), (long) (im.zoom * im.yimg));
3671 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3674 if (im.rendered_image) {
3677 img.u_blo.size = im.rendered_image_size;
3678 img.u_blo.ptr = im.rendered_image;
3679 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3686 void rrd_graph_init(
3695 #ifdef HAVE_SETLOCALE
3696 setlocale(LC_TIME, "");
3697 #ifdef HAVE_MBSTOWCS
3698 setlocale(LC_CTYPE, "");
3703 im->draw_x_grid = 1;
3704 im->draw_y_grid = 1;
3705 im->extra_flags = 0;
3706 im->font_options = cairo_font_options_create();
3707 im->forceleftspace = 0;
3710 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3711 im->grid_dash_off = 1;
3712 im->grid_dash_on = 1;
3714 im->grinfo = (info_t *) NULL;
3715 im->grinfo_current = (info_t *) NULL;
3716 im->imgformat = IF_PNG;
3719 im->logarithmic = 0;
3725 im->rendered_image_size = 0;
3726 im->rendered_image = NULL;
3731 im->tabwidth = 40.0;
3732 im->title[0] = '\0';
3733 im->unitsexponent = 9999;
3734 im->unitslength = 6;
3735 im->viewfactor = 1.0;
3736 im->watermark[0] = '\0';
3738 im->xlab_user.minsec = -1;
3741 im->ygridstep = DNAN;
3743 im->ylegend[0] = '\0';
3747 cairo_font_options_set_hint_style
3748 (im->font_options, CAIRO_HINT_STYLE_FULL);
3749 cairo_font_options_set_hint_metrics
3750 (im->font_options, CAIRO_HINT_METRICS_ON);
3751 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3752 for (i = 0; i < DIM(graph_col); i++)
3753 im->graph_col[i] = graph_col[i];
3754 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3757 char rrd_win_default_font[1000];
3759 windir = getenv("windir");
3760 /* %windir% is something like D:\windows or C:\winnt */
3761 if (windir != NULL) {
3762 strncpy(rrd_win_default_font, windir, 500);
3763 rrd_win_default_font[500] = '\0';
3764 strcat(rrd_win_default_font, "\\fonts\\");
3765 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3766 for (i = 0; i < DIM(text_prop); i++) {
3767 strncpy(text_prop[i].font,
3768 rrd_win_default_font, sizeof(text_prop[i].font) - 1);
3769 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3777 deffont = getenv("RRD_DEFAULT_FONT");
3778 if (deffont != NULL) {
3779 for (i = 0; i < DIM(text_prop); i++) {
3780 strncpy(text_prop[i].font, deffont,
3781 sizeof(text_prop[i].font) - 1);
3782 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3786 for (i = 0; i < DIM(text_prop); i++) {
3787 im->text_prop[i].size = text_prop[i].size;
3788 strcpy(im->text_prop[i].font, text_prop[i].font);
3792 void rrd_graph_options(
3799 char *parsetime_error = NULL;
3800 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3801 time_t start_tmp = 0, end_tmp = 0;
3803 struct rrd_time_value start_tv, end_tv;
3804 long unsigned int color;
3805 char *old_locale = "";
3807 /* defines for long options without a short equivalent. should be bytes,
3808 and may not collide with (the ASCII value of) short options */
3809 #define LONGOPT_UNITS_SI 255
3810 struct option long_options[] = {
3812 "start", required_argument, 0, 's'}, {
3813 "end", required_argument, 0,
3816 required_argument, 0,
3830 "height", required_argument, 0, 'h'}, {
3831 "full-size-mode", no_argument,
3849 "base", required_argument, 0, 'b'}, {
3850 "logarithmic", no_argument, 0,
3853 required_argument, 0,
3862 "imginfo", required_argument, 0, 'f'}, {
3864 required_argument, 0, 'a'}, {
3870 "zoom", required_argument, 0, 'm'}, {
3871 "no-legend", no_argument, 0,
3873 "force-rules-legend",
3883 "no-minor", no_argument, 0, 'I'}, {
3884 "slope-mode", no_argument, 0,
3887 no_argument, 0, 'A'}, {
3888 "alt-autoscale-min",
3892 "alt-autoscale-max",
3901 "units-exponent", required_argument,
3903 "units-length", required_argument,
3905 "units", required_argument, 0,
3906 LONGOPT_UNITS_SI}, {
3907 "step", required_argument, 0,
3910 required_argument, 0,
3915 "graph-render-mode",
3920 "font-smoothing-threshold",
3921 required_argument, 0, 'B'}, {
3922 "watermark", required_argument, 0, 'W'}, {
3923 "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 */
3928 opterr = 0; /* initialize getopt */
3929 parsetime("end-24h", &start_tv);
3930 parsetime("now", &end_tv);
3932 int option_index = 0;
3934 int col_start, col_end;
3936 opt = getopt_long(argc, argv,
3937 "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:k",
3938 long_options, &option_index);
3943 im->extra_flags |= NOMINOR;
3946 im->extra_flags |= ALTYGRID;
3949 im->extra_flags |= ALTAUTOSCALE;
3952 im->extra_flags |= ALTAUTOSCALE_MIN;
3955 im->extra_flags |= ALTAUTOSCALE_MAX;
3958 im->extra_flags |= ONLY_GRAPH;
3961 im->extra_flags |= NOLEGEND;
3964 im->extra_flags |= FORCE_RULES_LEGEND;
3966 case LONGOPT_UNITS_SI:
3967 if (im->extra_flags & FORCE_UNITS) {
3968 rrd_set_error("--units can only be used once!");
3969 setlocale(LC_NUMERIC, old_locale);
3972 if (strcmp(optarg, "si") == 0)
3973 im->extra_flags |= FORCE_UNITS_SI;
3975 rrd_set_error("invalid argument for --units: %s", optarg);
3980 im->unitsexponent = atoi(optarg);
3983 im->unitslength = atoi(optarg);
3984 im->forceleftspace = 1;
3987 old_locale = setlocale(LC_NUMERIC, "C");
3988 im->tabwidth = atof(optarg);
3989 setlocale(LC_NUMERIC, old_locale);
3992 old_locale = setlocale(LC_NUMERIC, "C");
3993 im->step = atoi(optarg);
3994 setlocale(LC_NUMERIC, old_locale);
4000 if ((parsetime_error = parsetime(optarg, &start_tv))) {
4001 rrd_set_error("start time: %s", parsetime_error);
4006 if ((parsetime_error = parsetime(optarg, &end_tv))) {
4007 rrd_set_error("end time: %s", parsetime_error);
4012 if (strcmp(optarg, "none") == 0) {
4013 im->draw_x_grid = 0;
4017 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4019 &im->xlab_user.gridst,
4021 &im->xlab_user.mgridst,
4023 &im->xlab_user.labst,
4024 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4025 strncpy(im->xlab_form, optarg + stroff,
4026 sizeof(im->xlab_form) - 1);
4027 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4029 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4030 rrd_set_error("unknown keyword %s", scan_gtm);
4033 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4035 rrd_set_error("unknown keyword %s", scan_mtm);
4038 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4039 rrd_set_error("unknown keyword %s", scan_ltm);
4042 im->xlab_user.minsec = 1;
4043 im->xlab_user.stst = im->xlab_form;
4045 rrd_set_error("invalid x-grid format");
4051 if (strcmp(optarg, "none") == 0) {
4052 im->draw_y_grid = 0;
4055 old_locale = setlocale(LC_NUMERIC, "C");
4056 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4057 setlocale(LC_NUMERIC, old_locale);
4058 if (im->ygridstep <= 0) {
4059 rrd_set_error("grid step must be > 0");
4061 } else if (im->ylabfact < 1) {
4062 rrd_set_error("label factor must be > 0");
4066 setlocale(LC_NUMERIC, old_locale);
4067 rrd_set_error("invalid y-grid format");
4072 strncpy(im->ylegend, optarg, 150);
4073 im->ylegend[150] = '\0';
4076 old_locale = setlocale(LC_NUMERIC, "C");
4077 im->maxval = atof(optarg);
4078 setlocale(LC_NUMERIC, old_locale);
4081 old_locale = setlocale(LC_NUMERIC, "C");
4082 im->minval = atof(optarg);
4083 setlocale(LC_NUMERIC, old_locale);
4086 im->base = atol(optarg);
4087 if (im->base != 1024 && im->base != 1000) {
4089 ("the only sensible value for base apart from 1000 is 1024");
4094 long_tmp = atol(optarg);
4095 if (long_tmp < 10) {
4096 rrd_set_error("width below 10 pixels");
4099 im->xsize = long_tmp;
4102 long_tmp = atol(optarg);
4103 if (long_tmp < 10) {
4104 rrd_set_error("height below 10 pixels");
4107 im->ysize = long_tmp;
4110 im->extra_flags |= FULL_SIZE_MODE;
4113 /* interlaced png not supported at the moment */
4119 im->imginfo = optarg;
4123 (im->imgformat = if_conv(optarg)) == -1) {
4124 rrd_set_error("unsupported graphics format '%s'", optarg);
4135 im->logarithmic = 1;
4139 "%10[A-Z]#%n%8lx%n",
4140 col_nam, &col_start, &color, &col_end) == 2) {
4142 int col_len = col_end - col_start;
4147 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4155 (((color & 0xF000) *
4156 0x11000) | ((color & 0x0F00) *
4157 0x01100) | ((color &
4160 ((color & 0x000F) * 0x00011)
4164 color = (color << 8) + 0xff /* shift left by 8 */ ;
4169 rrd_set_error("the color format is #RRGGBB[AA]");
4172 if ((ci = grc_conv(col_nam)) != -1) {
4173 im->graph_col[ci] = gfx_hex_to_col(color);
4175 rrd_set_error("invalid color name '%s'", col_nam);
4179 rrd_set_error("invalid color def format");
4188 old_locale = setlocale(LC_NUMERIC, "C");
4189 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4190 int sindex, propidx;
4192 setlocale(LC_NUMERIC, old_locale);
4193 if ((sindex = text_prop_conv(prop)) != -1) {
4194 for (propidx = sindex;
4195 propidx < TEXT_PROP_LAST; propidx++) {
4197 im->text_prop[propidx].size = size;
4199 if ((int) strlen(prop) > end) {
4200 if (prop[end] == ':') {
4201 strncpy(im->text_prop[propidx].font,
4202 prop + end + 1, 255);
4203 im->text_prop[propidx].font[255] = '\0';
4206 ("expected after font size in '%s'",
4211 if (propidx == sindex && sindex != 0)
4215 rrd_set_error("invalid fonttag '%s'", prop);
4219 setlocale(LC_NUMERIC, old_locale);
4220 rrd_set_error("invalid text property format");
4226 old_locale = setlocale(LC_NUMERIC, "C");
4227 im->zoom = atof(optarg);
4228 setlocale(LC_NUMERIC, old_locale);
4229 if (im->zoom <= 0.0) {
4230 rrd_set_error("zoom factor must be > 0");
4235 strncpy(im->title, optarg, 150);
4236 im->title[150] = '\0';
4239 if (strcmp(optarg, "normal") == 0) {
4240 cairo_font_options_set_antialias
4241 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4242 cairo_font_options_set_hint_style
4243 (im->font_options, CAIRO_HINT_STYLE_FULL);
4244 } else if (strcmp(optarg, "light") == 0) {
4245 cairo_font_options_set_antialias
4246 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4247 cairo_font_options_set_hint_style
4248 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4249 } else if (strcmp(optarg, "mono") == 0) {
4250 cairo_font_options_set_antialias
4251 (im->font_options, CAIRO_ANTIALIAS_NONE);
4252 cairo_font_options_set_hint_style
4253 (im->font_options, CAIRO_HINT_STYLE_FULL);
4255 rrd_set_error("unknown font-render-mode '%s'", optarg);
4260 if (strcmp(optarg, "normal") == 0)
4261 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4262 else if (strcmp(optarg, "mono") == 0)
4263 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4265 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4270 /* not supported curently */
4273 strncpy(im->watermark, optarg, 100);
4274 im->watermark[99] = '\0';
4278 rrd_set_error("unknown option '%c'", optopt);
4280 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4285 if (im->logarithmic && im->minval <= 0) {
4287 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4291 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4292 /* error string is set in parsetime.c */
4296 if (start_tmp < 3600 * 24 * 365 * 10) {
4298 ("the first entry to fetch should be after 1980 (%ld)",
4303 if (end_tmp < start_tmp) {
4305 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4309 im->start = start_tmp;
4311 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4314 int rrd_graph_color(
4322 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4324 color = strstr(var, "#");
4325 if (color == NULL) {
4326 if (optional == 0) {
4327 rrd_set_error("Found no color in %s", err);
4334 long unsigned int col;
4336 rest = strstr(color, ":");
4343 sscanf(color, "#%6lx%n", &col, &n);
4344 col = (col << 8) + 0xff /* shift left by 8 */ ;
4346 rrd_set_error("Color problem in %s", err);
4349 sscanf(color, "#%8lx%n", &col, &n);
4353 rrd_set_error("Color problem in %s", err);
4355 if (rrd_test_error())
4357 gdp->col = gfx_hex_to_col(col);
4370 while (*ptr != '\0')
4371 if (*ptr++ == '%') {
4373 /* line cannot end with percent char */
4376 /* '%s', '%S' and '%%' are allowed */
4377 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4379 /* %c is allowed (but use only with vdef!) */
4380 else if (*ptr == 'c') {
4385 /* or else '% 6.2lf' and such are allowed */
4387 /* optional padding character */
4388 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4390 /* This should take care of 'm.n' with all three optional */
4391 while (*ptr >= '0' && *ptr <= '9')
4395 while (*ptr >= '0' && *ptr <= '9')
4397 /* Either 'le', 'lf' or 'lg' must follow here */
4400 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4415 const char *const str)
4417 /* A VDEF currently is either "func" or "param,func"
4418 * so the parsing is rather simple. Change if needed.
4426 old_locale = setlocale(LC_NUMERIC, "C");
4427 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4428 setlocale(LC_NUMERIC, old_locale);
4429 if (n == (int) strlen(str)) { /* matched */
4433 sscanf(str, "%29[A-Z]%n", func, &n);
4434 if (n == (int) strlen(str)) { /* matched */
4438 ("Unknown function string '%s' in VDEF '%s'",
4443 if (!strcmp("PERCENT", func))
4444 gdes->vf.op = VDEF_PERCENT;
4445 else if (!strcmp("MAXIMUM", func))
4446 gdes->vf.op = VDEF_MAXIMUM;
4447 else if (!strcmp("AVERAGE", func))
4448 gdes->vf.op = VDEF_AVERAGE;
4449 else if (!strcmp("STDEV", func))
4450 gdes->vf.op = VDEF_STDEV;
4451 else if (!strcmp("MINIMUM", func))
4452 gdes->vf.op = VDEF_MINIMUM;
4453 else if (!strcmp("TOTAL", func))
4454 gdes->vf.op = VDEF_TOTAL;
4455 else if (!strcmp("FIRST", func))
4456 gdes->vf.op = VDEF_FIRST;
4457 else if (!strcmp("LAST", func))
4458 gdes->vf.op = VDEF_LAST;
4459 else if (!strcmp("LSLSLOPE", func))
4460 gdes->vf.op = VDEF_LSLSLOPE;
4461 else if (!strcmp("LSLINT", func))
4462 gdes->vf.op = VDEF_LSLINT;
4463 else if (!strcmp("LSLCORREL", func))
4464 gdes->vf.op = VDEF_LSLCORREL;
4467 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4470 switch (gdes->vf.op) {
4472 if (isnan(param)) { /* no parameter given */
4474 ("Function '%s' needs parameter in VDEF '%s'\n",
4478 if (param >= 0.0 && param <= 100.0) {
4479 gdes->vf.param = param;
4480 gdes->vf.val = DNAN; /* undefined */
4481 gdes->vf.when = 0; /* undefined */
4484 ("Parameter '%f' out of range in VDEF '%s'\n",
4485 param, gdes->vname);
4498 case VDEF_LSLCORREL:
4500 gdes->vf.param = DNAN;
4501 gdes->vf.val = DNAN;
4505 ("Function '%s' needs no parameter in VDEF '%s'\n",
4519 graph_desc_t *src, *dst;
4523 dst = &im->gdes[gdi];
4524 src = &im->gdes[dst->vidx];
4525 data = src->data + src->ds;
4526 steps = (src->end - src->start) / src->step;
4529 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4530 src->start, src->end, steps);
4532 switch (dst->vf.op) {
4536 if ((array = malloc(steps * sizeof(double))) == NULL) {
4537 rrd_set_error("malloc VDEV_PERCENT");
4540 for (step = 0; step < steps; step++) {
4541 array[step] = data[step * src->ds_cnt];
4543 qsort(array, step, sizeof(double), vdef_percent_compar);
4544 field = (steps - 1) * dst->vf.param / 100;
4545 dst->vf.val = array[field];
4546 dst->vf.when = 0; /* no time component */
4549 for (step = 0; step < steps; step++)
4550 printf("DEBUG: %3li:%10.2f %c\n",
4551 step, array[step], step == field ? '*' : ' ');
4557 while (step != steps && isnan(data[step * src->ds_cnt]))
4559 if (step == steps) {
4563 dst->vf.val = data[step * src->ds_cnt];
4564 dst->vf.when = src->start + (step + 1) * src->step;
4566 while (step != steps) {
4567 if (finite(data[step * src->ds_cnt])) {
4568 if (data[step * src->ds_cnt] > dst->vf.val) {
4569 dst->vf.val = data[step * src->ds_cnt];
4570 dst->vf.when = src->start + (step + 1) * src->step;
4581 double average = 0.0;
4583 for (step = 0; step < steps; step++) {
4584 if (finite(data[step * src->ds_cnt])) {
4585 sum += data[step * src->ds_cnt];
4590 if (dst->vf.op == VDEF_TOTAL) {
4591 dst->vf.val = sum * src->step;
4592 dst->vf.when = 0; /* no time component */
4593 } else if (dst->vf.op == VDEF_AVERAGE) {
4594 dst->vf.val = sum / cnt;
4595 dst->vf.when = 0; /* no time component */
4597 average = sum / cnt;
4599 for (step = 0; step < steps; step++) {
4600 if (finite(data[step * src->ds_cnt])) {
4601 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4604 dst->vf.val = pow(sum / cnt, 0.5);
4605 dst->vf.when = 0; /* no time component */
4615 while (step != steps && isnan(data[step * src->ds_cnt]))
4617 if (step == steps) {
4621 dst->vf.val = data[step * src->ds_cnt];
4622 dst->vf.when = src->start + (step + 1) * src->step;
4624 while (step != steps) {
4625 if (finite(data[step * src->ds_cnt])) {
4626 if (data[step * src->ds_cnt] < dst->vf.val) {
4627 dst->vf.val = data[step * src->ds_cnt];
4628 dst->vf.when = src->start + (step + 1) * src->step;
4635 /* The time value returned here is one step before the
4636 * actual time value. This is the start of the first
4640 while (step != steps && isnan(data[step * src->ds_cnt]))
4642 if (step == steps) { /* all entries were NaN */
4646 dst->vf.val = data[step * src->ds_cnt];
4647 dst->vf.when = src->start + step * src->step;
4651 /* The time value returned here is the
4652 * actual time value. This is the end of the last
4656 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4658 if (step < 0) { /* all entries were NaN */
4662 dst->vf.val = data[step * src->ds_cnt];
4663 dst->vf.when = src->start + (step + 1) * src->step;
4668 case VDEF_LSLCORREL:{
4669 /* Bestfit line by linear least squares method */
4672 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4679 for (step = 0; step < steps; step++) {
4680 if (finite(data[step * src->ds_cnt])) {
4683 SUMxx += step * step;
4684 SUMxy += step * data[step * src->ds_cnt];
4685 SUMy += data[step * src->ds_cnt];
4686 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4690 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4691 y_intercept = (SUMy - slope * SUMx) / cnt;
4694 (SUMx * SUMy) / cnt) /
4696 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4698 if (dst->vf.op == VDEF_LSLSLOPE) {
4699 dst->vf.val = slope;
4701 } else if (dst->vf.op == VDEF_LSLINT) {
4702 dst->vf.val = y_intercept;
4704 } else if (dst->vf.op == VDEF_LSLCORREL) {
4705 dst->vf.val = correl;
4718 /* NaN < -INF < finite_values < INF */
4719 int vdef_percent_compar(
4725 /* Equality is not returned; this doesn't hurt except
4726 * (maybe) for a little performance.
4729 /* First catch NaN values. They are smallest */
4730 if (isnan(*(double *) a))
4732 if (isnan(*(double *) b))
4734 /* NaN doesn't reach this part so INF and -INF are extremes.
4735 * The sign from isinf() is compatible with the sign we return
4737 if (isinf(*(double *) a))
4738 return isinf(*(double *) a);
4739 if (isinf(*(double *) b))
4740 return isinf(*(double *) b);
4741 /* If we reach this, both values must be finite */
4742 if (*(double *) a < *(double *) b)
4751 enum info_type type,
4754 im->grinfo_current = info_push(im->grinfo_current, key, type, value);
4755 if (im->grinfo == NULL) {
4756 im->grinfo = im->grinfo_current;