1 /****************************************************************************
2 * RRDtool 1.3rc4 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));
209 /* conversion function for symbolic entry names */
212 #define conv_if(VV,VVV) \
213 if (strcmp(#VV, string) == 0) return VVV ;
219 conv_if(PRINT, GF_PRINT);
220 conv_if(GPRINT, GF_GPRINT);
221 conv_if(COMMENT, GF_COMMENT);
222 conv_if(HRULE, GF_HRULE);
223 conv_if(VRULE, GF_VRULE);
224 conv_if(LINE, GF_LINE);
225 conv_if(AREA, GF_AREA);
226 conv_if(STACK, GF_STACK);
227 conv_if(TICK, GF_TICK);
228 conv_if(TEXTALIGN, GF_TEXTALIGN);
229 conv_if(DEF, GF_DEF);
230 conv_if(CDEF, GF_CDEF);
231 conv_if(VDEF, GF_VDEF);
232 conv_if(XPORT, GF_XPORT);
233 conv_if(SHIFT, GF_SHIFT);
238 enum gfx_if_en if_conv(
242 conv_if(PNG, IF_PNG);
243 conv_if(SVG, IF_SVG);
244 conv_if(EPS, IF_EPS);
245 conv_if(PDF, IF_PDF);
250 enum tmt_en tmt_conv(
254 conv_if(SECOND, TMT_SECOND);
255 conv_if(MINUTE, TMT_MINUTE);
256 conv_if(HOUR, TMT_HOUR);
257 conv_if(DAY, TMT_DAY);
258 conv_if(WEEK, TMT_WEEK);
259 conv_if(MONTH, TMT_MONTH);
260 conv_if(YEAR, TMT_YEAR);
264 enum grc_en grc_conv(
268 conv_if(BACK, GRC_BACK);
269 conv_if(CANVAS, GRC_CANVAS);
270 conv_if(SHADEA, GRC_SHADEA);
271 conv_if(SHADEB, GRC_SHADEB);
272 conv_if(GRID, GRC_GRID);
273 conv_if(MGRID, GRC_MGRID);
274 conv_if(FONT, GRC_FONT);
275 conv_if(ARROW, GRC_ARROW);
276 conv_if(AXIS, GRC_AXIS);
277 conv_if(FRAME, GRC_FRAME);
282 enum text_prop_en text_prop_conv(
286 conv_if(DEFAULT, TEXT_PROP_DEFAULT);
287 conv_if(TITLE, TEXT_PROP_TITLE);
288 conv_if(AXIS, TEXT_PROP_AXIS);
289 conv_if(UNIT, TEXT_PROP_UNIT);
290 conv_if(LEGEND, TEXT_PROP_LEGEND);
301 cairo_status_t status = 0;
305 for (i = 0; i < (unsigned) im->gdes_c; i++) {
306 if (im->gdes[i].data_first) {
307 /* careful here, because a single pointer can occur several times */
308 free(im->gdes[i].data);
309 if (im->gdes[i].ds_namv) {
310 for (ii = 0; ii < im->gdes[i].ds_cnt; ii++)
311 free(im->gdes[i].ds_namv[ii]);
312 free(im->gdes[i].ds_namv);
315 /* free allocated memory used for dashed lines */
316 if (im->gdes[i].p_dashes != NULL)
317 free(im->gdes[i].p_dashes);
319 free(im->gdes[i].p_data);
320 free(im->gdes[i].rpnp);
323 if (im->font_options)
324 cairo_font_options_destroy(im->font_options);
327 status = cairo_status(im->cr);
328 cairo_destroy(im->cr);
330 if (im->rendered_image) {
331 free(im->rendered_image);
334 cairo_surface_destroy(im->surface);
336 fprintf(stderr, "OOPS: Cairo has issues it can't even die: %s\n",
337 cairo_status_to_string(status));
342 /* find SI magnitude symbol for the given number*/
344 image_desc_t *im, /* image description */
350 char *symbol[] = { "a", /* 10e-18 Atto */
351 "f", /* 10e-15 Femto */
352 "p", /* 10e-12 Pico */
353 "n", /* 10e-9 Nano */
354 "u", /* 10e-6 Micro */
355 "m", /* 10e-3 Milli */
360 "T", /* 10e12 Tera */
361 "P", /* 10e15 Peta */
368 if (*value == 0.0 || isnan(*value)) {
372 sindex = floor(log(fabs(*value)) / log((double) im->base));
373 *magfact = pow((double) im->base, (double) sindex);
374 (*value) /= (*magfact);
376 if (sindex <= symbcenter && sindex >= -symbcenter) {
377 (*symb_ptr) = symbol[sindex + symbcenter];
384 static char si_symbol[] = {
385 'a', /* 10e-18 Atto */
386 'f', /* 10e-15 Femto */
387 'p', /* 10e-12 Pico */
388 'n', /* 10e-9 Nano */
389 'u', /* 10e-6 Micro */
390 'm', /* 10e-3 Milli */
395 'T', /* 10e12 Tera */
396 'P', /* 10e15 Peta */
399 static const int si_symbcenter = 6;
401 /* find SI magnitude symbol for the numbers on the y-axis*/
403 image_desc_t *im /* image description */
407 double digits, viewdigits = 0;
410 floor(log(max(fabs(im->minval), fabs(im->maxval))) /
411 log((double) im->base));
413 if (im->unitsexponent != 9999) {
414 /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
415 viewdigits = floor(im->unitsexponent / 3);
420 im->magfact = pow((double) im->base, digits);
423 printf("digits %6.3f im->magfact %6.3f\n", digits, im->magfact);
426 im->viewfactor = im->magfact / pow((double) im->base, viewdigits);
428 if (((viewdigits + si_symbcenter) < sizeof(si_symbol)) &&
429 ((viewdigits + si_symbcenter) >= 0))
430 im->symbol = si_symbol[(int) viewdigits + si_symbcenter];
435 /* move min and max values around to become sensible */
440 double sensiblevalues[] = { 1000.0, 900.0, 800.0, 750.0, 700.0,
441 600.0, 500.0, 400.0, 300.0, 250.0,
442 200.0, 125.0, 100.0, 90.0, 80.0,
443 75.0, 70.0, 60.0, 50.0, 40.0, 30.0,
444 25.0, 20.0, 10.0, 9.0, 8.0,
445 7.0, 6.0, 5.0, 4.0, 3.5, 3.0,
446 2.5, 2.0, 1.8, 1.5, 1.2, 1.0,
447 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, -1
450 double scaled_min, scaled_max;
457 printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
458 im->minval, im->maxval, im->magfact);
461 if (isnan(im->ygridstep)) {
462 if (im->extra_flags & ALTAUTOSCALE) {
463 /* measure the amplitude of the function. Make sure that
464 graph boundaries are slightly higher then max/min vals
465 so we can see amplitude on the graph */
468 delt = im->maxval - im->minval;
470 fact = 2.0 * pow(10.0,
472 (max(fabs(im->minval), fabs(im->maxval)) /
475 adj = (fact - delt) * 0.55;
478 ("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n",
479 im->minval, im->maxval, delt, fact, adj);
484 } else if (im->extra_flags & ALTAUTOSCALE_MIN) {
485 /* measure the amplitude of the function. Make sure that
486 graph boundaries are slightly lower than min vals
487 so we can see amplitude on the graph */
488 adj = (im->maxval - im->minval) * 0.1;
490 } else if (im->extra_flags & ALTAUTOSCALE_MAX) {
491 /* measure the amplitude of the function. Make sure that
492 graph boundaries are slightly higher than max vals
493 so we can see amplitude on the graph */
494 adj = (im->maxval - im->minval) * 0.1;
497 scaled_min = im->minval / im->magfact;
498 scaled_max = im->maxval / im->magfact;
500 for (i = 1; sensiblevalues[i] > 0; i++) {
501 if (sensiblevalues[i - 1] >= scaled_min &&
502 sensiblevalues[i] <= scaled_min)
503 im->minval = sensiblevalues[i] * (im->magfact);
505 if (-sensiblevalues[i - 1] <= scaled_min &&
506 -sensiblevalues[i] >= scaled_min)
507 im->minval = -sensiblevalues[i - 1] * (im->magfact);
509 if (sensiblevalues[i - 1] >= scaled_max &&
510 sensiblevalues[i] <= scaled_max)
511 im->maxval = sensiblevalues[i - 1] * (im->magfact);
513 if (-sensiblevalues[i - 1] <= scaled_max &&
514 -sensiblevalues[i] >= scaled_max)
515 im->maxval = -sensiblevalues[i] * (im->magfact);
519 /* adjust min and max to the grid definition if there is one */
520 im->minval = (double) im->ylabfact * im->ygridstep *
521 floor(im->minval / ((double) im->ylabfact * im->ygridstep));
522 im->maxval = (double) im->ylabfact * im->ygridstep *
523 ceil(im->maxval / ((double) im->ylabfact * im->ygridstep));
527 fprintf(stderr, "SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
528 im->minval, im->maxval, im->magfact);
536 if (isnan(im->minval) || isnan(im->maxval))
539 if (im->logarithmic) {
540 double ya, yb, ypix, ypixfrac;
541 double log10_range = log10(im->maxval) - log10(im->minval);
543 ya = pow((double) 10, floor(log10(im->minval)));
544 while (ya < im->minval)
547 return; /* don't have y=10^x gridline */
549 if (yb <= im->maxval) {
550 /* we have at least 2 y=10^x gridlines.
551 Make sure distance between them in pixels
552 are an integer by expanding im->maxval */
553 double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
554 double factor = y_pixel_delta / floor(y_pixel_delta);
555 double new_log10_range = factor * log10_range;
556 double new_ymax_log10 = log10(im->minval) + new_log10_range;
558 im->maxval = pow(10, new_ymax_log10);
559 ytr(im, DNAN); /* reset precalc */
560 log10_range = log10(im->maxval) - log10(im->minval);
562 /* make sure first y=10^x gridline is located on
563 integer pixel position by moving scale slightly
564 downwards (sub-pixel movement) */
565 ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
566 ypixfrac = ypix - floor(ypix);
567 if (ypixfrac > 0 && ypixfrac < 1) {
568 double yfrac = ypixfrac / im->ysize;
570 im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
571 im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
572 ytr(im, DNAN); /* reset precalc */
575 /* Make sure we have an integer pixel distance between
576 each minor gridline */
577 double ypos1 = ytr(im, im->minval);
578 double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
579 double y_pixel_delta = ypos1 - ypos2;
580 double factor = y_pixel_delta / floor(y_pixel_delta);
581 double new_range = factor * (im->maxval - im->minval);
582 double gridstep = im->ygrid_scale.gridstep;
583 double minor_y, minor_y_px, minor_y_px_frac;
585 if (im->maxval > 0.0)
586 im->maxval = im->minval + new_range;
588 im->minval = im->maxval - new_range;
589 ytr(im, DNAN); /* reset precalc */
590 /* make sure first minor gridline is on integer pixel y coord */
591 minor_y = gridstep * floor(im->minval / gridstep);
592 while (minor_y < im->minval)
594 minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
595 minor_y_px_frac = minor_y_px - floor(minor_y_px);
596 if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
597 double yfrac = minor_y_px_frac / im->ysize;
598 double range = im->maxval - im->minval;
600 im->minval = im->minval - yfrac * range;
601 im->maxval = im->maxval - yfrac * range;
602 ytr(im, DNAN); /* reset precalc */
604 calc_horizontal_grid(im); /* recalc with changed im->maxval */
608 /* reduce data reimplementation by Alex */
611 enum cf_en cf, /* which consolidation function ? */
612 unsigned long cur_step, /* step the data currently is in */
613 time_t *start, /* start, end and step as requested ... */
614 time_t *end, /* ... by the application will be ... */
615 unsigned long *step, /* ... adjusted to represent reality */
616 unsigned long *ds_cnt, /* number of data sources in file */
618 { /* two dimensional array containing the data */
619 int i, reduce_factor = ceil((double) (*step) / (double) cur_step);
620 unsigned long col, dst_row, row_cnt, start_offset, end_offset, skiprows =
622 rrd_value_t *srcptr, *dstptr;
624 (*step) = cur_step * reduce_factor; /* set new step size for reduced data */
627 row_cnt = ((*end) - (*start)) / cur_step;
633 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
634 row_cnt, reduce_factor, *start, *end, cur_step);
635 for (col = 0; col < row_cnt; col++) {
636 printf("time %10lu: ", *start + (col + 1) * cur_step);
637 for (i = 0; i < *ds_cnt; i++)
638 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
643 /* We have to combine [reduce_factor] rows of the source
644 ** into one row for the destination. Doing this we also
645 ** need to take care to combine the correct rows. First
646 ** alter the start and end time so that they are multiples
647 ** of the new step time. We cannot reduce the amount of
648 ** time so we have to move the end towards the future and
649 ** the start towards the past.
651 end_offset = (*end) % (*step);
652 start_offset = (*start) % (*step);
654 /* If there is a start offset (which cannot be more than
655 ** one destination row), skip the appropriate number of
656 ** source rows and one destination row. The appropriate
657 ** number is what we do know (start_offset/cur_step) of
658 ** the new interval (*step/cur_step aka reduce_factor).
661 printf("start_offset: %lu end_offset: %lu\n", start_offset, end_offset);
662 printf("row_cnt before: %lu\n", row_cnt);
665 (*start) = (*start) - start_offset;
666 skiprows = reduce_factor - start_offset / cur_step;
667 srcptr += skiprows * *ds_cnt;
668 for (col = 0; col < (*ds_cnt); col++)
673 printf("row_cnt between: %lu\n", row_cnt);
676 /* At the end we have some rows that are not going to be
677 ** used, the amount is end_offset/cur_step
680 (*end) = (*end) - end_offset + (*step);
681 skiprows = end_offset / cur_step;
685 printf("row_cnt after: %lu\n", row_cnt);
688 /* Sanity check: row_cnt should be multiple of reduce_factor */
689 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
691 if (row_cnt % reduce_factor) {
692 printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
693 row_cnt, reduce_factor);
694 printf("BUG in reduce_data()\n");
698 /* Now combine reduce_factor intervals at a time
699 ** into one interval for the destination.
702 for (dst_row = 0; (long int) row_cnt >= reduce_factor; dst_row++) {
703 for (col = 0; col < (*ds_cnt); col++) {
704 rrd_value_t newval = DNAN;
705 unsigned long validval = 0;
707 for (i = 0; i < reduce_factor; i++) {
708 if (isnan(srcptr[i * (*ds_cnt) + col])) {
713 newval = srcptr[i * (*ds_cnt) + col];
722 newval += srcptr[i * (*ds_cnt) + col];
725 newval = min(newval, srcptr[i * (*ds_cnt) + col]);
728 /* an interval contains a failure if any subintervals contained a failure */
730 newval = max(newval, srcptr[i * (*ds_cnt) + col]);
733 newval = srcptr[i * (*ds_cnt) + col];
759 srcptr += (*ds_cnt) * reduce_factor;
760 row_cnt -= reduce_factor;
762 /* If we had to alter the endtime, we didn't have enough
763 ** source rows to fill the last row. Fill it with NaN.
766 for (col = 0; col < (*ds_cnt); col++)
769 row_cnt = ((*end) - (*start)) / *step;
771 printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
772 row_cnt, *start, *end, *step);
773 for (col = 0; col < row_cnt; col++) {
774 printf("time %10lu: ", *start + (col + 1) * (*step));
775 for (i = 0; i < *ds_cnt; i++)
776 printf(" %8.2e", srcptr[*ds_cnt * col + i]);
783 /* get the data required for the graphs from the
792 /* pull the data from the rrd files ... */
793 for (i = 0; i < (int) im->gdes_c; i++) {
794 /* only GF_DEF elements fetch data */
795 if (im->gdes[i].gf != GF_DEF)
799 /* do we have it already ? */
800 for (ii = 0; ii < i; ii++) {
801 if (im->gdes[ii].gf != GF_DEF)
803 if ((strcmp(im->gdes[i].rrd, im->gdes[ii].rrd) == 0)
804 && (im->gdes[i].cf == im->gdes[ii].cf)
805 && (im->gdes[i].cf_reduce == im->gdes[ii].cf_reduce)
806 && (im->gdes[i].start_orig == im->gdes[ii].start_orig)
807 && (im->gdes[i].end_orig == im->gdes[ii].end_orig)
808 && (im->gdes[i].step_orig == im->gdes[ii].step_orig)) {
809 /* OK, the data is already there.
810 ** Just copy the header portion
812 im->gdes[i].start = im->gdes[ii].start;
813 im->gdes[i].end = im->gdes[ii].end;
814 im->gdes[i].step = im->gdes[ii].step;
815 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
816 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;
817 im->gdes[i].data = im->gdes[ii].data;
818 im->gdes[i].data_first = 0;
825 unsigned long ft_step = im->gdes[i].step; /* ft_step will record what we got from fetch */
827 if ((rrd_fetch_fn(im->gdes[i].rrd,
833 &im->gdes[i].ds_namv,
834 &im->gdes[i].data)) == -1) {
837 im->gdes[i].data_first = 1;
839 if (ft_step < im->gdes[i].step) {
840 reduce_data(im->gdes[i].cf_reduce,
845 &im->gdes[i].ds_cnt, &im->gdes[i].data);
847 im->gdes[i].step = ft_step;
851 /* lets see if the required data source is really there */
852 for (ii = 0; ii < (int) im->gdes[i].ds_cnt; ii++) {
853 if (strcmp(im->gdes[i].ds_namv[ii], im->gdes[i].ds_nam) == 0) {
857 if (im->gdes[i].ds == -1) {
858 rrd_set_error("No DS called '%s' in '%s'",
859 im->gdes[i].ds_nam, im->gdes[i].rrd);
867 /* evaluate the expressions in the CDEF functions */
869 /*************************************************************
871 *************************************************************/
873 long find_var_wrapper(
877 return find_var((image_desc_t *) arg1, key);
880 /* find gdes containing var*/
887 for (ii = 0; ii < im->gdes_c - 1; ii++) {
888 if ((im->gdes[ii].gf == GF_DEF
889 || im->gdes[ii].gf == GF_VDEF || im->gdes[ii].gf == GF_CDEF)
890 && (strcmp(im->gdes[ii].vname, key) == 0)) {
897 /* find the largest common denominator for all the numbers
898 in the 0 terminated num array */
905 for (i = 0; num[i + 1] != 0; i++) {
907 rest = num[i] % num[i + 1];
913 /* return i==0?num[i]:num[i-1]; */
917 /* run the rpn calculator on all the VDEF and CDEF arguments */
924 long *steparray, rpi;
929 rpnstack_init(&rpnstack);
931 for (gdi = 0; gdi < im->gdes_c; gdi++) {
932 /* Look for GF_VDEF and GF_CDEF in the same loop,
933 * so CDEFs can use VDEFs and vice versa
935 switch (im->gdes[gdi].gf) {
939 graph_desc_t *vdp = &im->gdes[im->gdes[gdi].vidx];
941 /* remove current shift */
942 vdp->start -= vdp->shift;
943 vdp->end -= vdp->shift;
946 if (im->gdes[gdi].shidx >= 0)
947 vdp->shift = im->gdes[im->gdes[gdi].shidx].vf.val;
950 vdp->shift = im->gdes[gdi].shval;
952 /* normalize shift to multiple of consolidated step */
953 vdp->shift = (vdp->shift / (long) vdp->step) * (long) vdp->step;
956 vdp->start += vdp->shift;
957 vdp->end += vdp->shift;
961 /* A VDEF has no DS. This also signals other parts
962 * of rrdtool that this is a VDEF value, not a CDEF.
964 im->gdes[gdi].ds_cnt = 0;
965 if (vdef_calc(im, gdi)) {
966 rrd_set_error("Error processing VDEF '%s'",
967 im->gdes[gdi].vname);
968 rpnstack_free(&rpnstack);
973 im->gdes[gdi].ds_cnt = 1;
974 im->gdes[gdi].ds = 0;
975 im->gdes[gdi].data_first = 1;
976 im->gdes[gdi].start = 0;
977 im->gdes[gdi].end = 0;
982 /* Find the variables in the expression.
983 * - VDEF variables are substituted by their values
984 * and the opcode is changed into OP_NUMBER.
985 * - CDEF variables are analized for their step size,
986 * the lowest common denominator of all the step
987 * sizes of the data sources involved is calculated
988 * and the resulting number is the step size for the
989 * resulting data source.
991 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
992 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
993 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
994 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
996 if (im->gdes[ptr].ds_cnt == 0) { /* this is a VDEF data source */
999 ("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
1000 im->gdes[gdi].vname, im->gdes[ptr].vname);
1001 printf("DEBUG: value from vdef is %f\n",
1002 im->gdes[ptr].vf.val);
1004 im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
1005 im->gdes[gdi].rpnp[rpi].op = OP_NUMBER;
1006 } else { /* normal variables and PREF(variables) */
1008 /* add one entry to the array that keeps track of the step sizes of the
1009 * data sources going into the CDEF. */
1011 rrd_realloc(steparray,
1013 1) * sizeof(*steparray))) == NULL) {
1014 rrd_set_error("realloc steparray");
1015 rpnstack_free(&rpnstack);
1019 steparray[stepcnt - 1] = im->gdes[ptr].step;
1021 /* adjust start and end of cdef (gdi) so
1022 * that it runs from the latest start point
1023 * to the earliest endpoint of any of the
1024 * rras involved (ptr)
1027 if (im->gdes[gdi].start < im->gdes[ptr].start)
1028 im->gdes[gdi].start = im->gdes[ptr].start;
1030 if (im->gdes[gdi].end == 0 ||
1031 im->gdes[gdi].end > im->gdes[ptr].end)
1032 im->gdes[gdi].end = im->gdes[ptr].end;
1034 /* store pointer to the first element of
1035 * the rra providing data for variable,
1036 * further save step size and data source
1039 im->gdes[gdi].rpnp[rpi].data =
1040 im->gdes[ptr].data + im->gdes[ptr].ds;
1041 im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
1042 im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
1044 /* backoff the *.data ptr; this is done so
1045 * rpncalc() function doesn't have to treat
1046 * the first case differently
1048 } /* if ds_cnt != 0 */
1049 } /* if OP_VARIABLE */
1050 } /* loop through all rpi */
1052 /* move the data pointers to the correct period */
1053 for (rpi = 0; im->gdes[gdi].rpnp[rpi].op != OP_END; rpi++) {
1054 if (im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE ||
1055 im->gdes[gdi].rpnp[rpi].op == OP_PREV_OTHER) {
1056 long ptr = im->gdes[gdi].rpnp[rpi].ptr;
1058 im->gdes[gdi].start - im->gdes[ptr].start;
1061 im->gdes[gdi].rpnp[rpi].data +=
1062 (diff / im->gdes[ptr].step) *
1063 im->gdes[ptr].ds_cnt;
1067 if (steparray == NULL) {
1068 rrd_set_error("rpn expressions without DEF"
1069 " or CDEF variables are not supported");
1070 rpnstack_free(&rpnstack);
1073 steparray[stepcnt] = 0;
1074 /* Now find the resulting step. All steps in all
1075 * used RRAs have to be visited
1077 im->gdes[gdi].step = lcd(steparray);
1079 if ((im->gdes[gdi].data = malloc(((im->gdes[gdi].end -
1080 im->gdes[gdi].start)
1081 / im->gdes[gdi].step)
1082 * sizeof(double))) == NULL) {
1083 rrd_set_error("malloc im->gdes[gdi].data");
1084 rpnstack_free(&rpnstack);
1088 /* Step through the new cdef results array and
1089 * calculate the values
1091 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
1092 now <= im->gdes[gdi].end; now += im->gdes[gdi].step) {
1093 rpnp_t *rpnp = im->gdes[gdi].rpnp;
1095 /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
1096 * in this case we are advancing by timesteps;
1097 * we use the fact that time_t is a synonym for long
1099 if (rpn_calc(rpnp, &rpnstack, (long) now,
1100 im->gdes[gdi].data, ++dataidx) == -1) {
1101 /* rpn_calc sets the error string */
1102 rpnstack_free(&rpnstack);
1105 } /* enumerate over time steps within a CDEF */
1110 } /* enumerate over CDEFs */
1111 rpnstack_free(&rpnstack);
1115 /* from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm */
1116 /* yes we are loosing precision by doing tos with floats instead of doubles
1117 but it seems more stable this way. */
1119 static int AlmostEqual2sComplement(
1125 int aInt = *(int *) &A;
1126 int bInt = *(int *) &B;
1129 /* Make sure maxUlps is non-negative and small enough that the
1130 default NAN won't compare as equal to anything. */
1132 /* assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024); */
1134 /* Make aInt lexicographically ordered as a twos-complement int */
1137 aInt = 0x80000000l - aInt;
1139 /* Make bInt lexicographically ordered as a twos-complement int */
1142 bInt = 0x80000000l - bInt;
1144 intDiff = abs(aInt - bInt);
1146 if (intDiff <= maxUlps)
1152 /* massage data so, that we get one value for each x coordinate in the graph */
1157 double pixstep = (double) (im->end - im->start)
1158 / (double) im->xsize; /* how much time
1159 passes in one pixel */
1161 double minval = DNAN, maxval = DNAN;
1163 unsigned long gr_time;
1165 /* memory for the processed data */
1166 for (i = 0; i < im->gdes_c; i++) {
1167 if ((im->gdes[i].gf == GF_LINE) ||
1168 (im->gdes[i].gf == GF_AREA) || (im->gdes[i].gf == GF_TICK)) {
1169 if ((im->gdes[i].p_data = malloc((im->xsize + 1)
1170 * sizeof(rrd_value_t))) == NULL) {
1171 rrd_set_error("malloc data_proc");
1177 for (i = 0; i < im->xsize; i++) { /* for each pixel */
1180 gr_time = im->start + pixstep * i; /* time of the current step */
1183 for (ii = 0; ii < im->gdes_c; ii++) {
1186 switch (im->gdes[ii].gf) {
1190 if (!im->gdes[ii].stack)
1192 value = im->gdes[ii].yrule;
1193 if (isnan(value) || (im->gdes[ii].gf == GF_TICK)) {
1194 /* The time of the data doesn't necessarily match
1195 ** the time of the graph. Beware.
1197 vidx = im->gdes[ii].vidx;
1198 if (im->gdes[vidx].gf == GF_VDEF) {
1199 value = im->gdes[vidx].vf.val;
1201 if (((long int) gr_time >=
1202 (long int) im->gdes[vidx].start)
1203 && ((long int) gr_time <=
1204 (long int) im->gdes[vidx].end)) {
1205 value = im->gdes[vidx].data[(unsigned long)
1211 im->gdes[vidx].step)
1212 * im->gdes[vidx].ds_cnt +
1219 if (!isnan(value)) {
1221 im->gdes[ii].p_data[i] = paintval;
1222 /* GF_TICK: the data values are not
1223 ** relevant for min and max
1225 if (finite(paintval) && im->gdes[ii].gf != GF_TICK) {
1226 if ((isnan(minval) || paintval < minval) &&
1227 !(im->logarithmic && paintval <= 0.0))
1229 if (isnan(maxval) || paintval > maxval)
1233 im->gdes[ii].p_data[i] = DNAN;
1238 ("STACK should already be turned into LINE or AREA here");
1247 /* if min or max have not been asigned a value this is because
1248 there was no data in the graph ... this is not good ...
1249 lets set these to dummy values then ... */
1251 if (im->logarithmic) {
1252 if (isnan(minval) || isnan(maxval) || maxval <= 0) {
1253 minval = 0.0; /* catching this right away below */
1256 /* in logarithm mode, where minval is smaller or equal
1257 to 0 make the beast just way smaller than maxval */
1259 minval = maxval / 10e8;
1262 if (isnan(minval) || isnan(maxval)) {
1268 /* adjust min and max values given by the user */
1269 /* for logscale we add something on top */
1270 if (isnan(im->minval)
1271 || ((!im->rigid) && im->minval > minval)
1273 if (im->logarithmic)
1274 im->minval = minval / 2.0;
1276 im->minval = minval;
1278 if (isnan(im->maxval)
1279 || (!im->rigid && im->maxval < maxval)
1281 if (im->logarithmic)
1282 im->maxval = maxval * 2.0;
1284 im->maxval = maxval;
1287 /* make sure min is smaller than max */
1288 if (im->minval > im->maxval) {
1290 im->minval = 0.99 * im->maxval;
1292 im->minval = 1.01 * im->maxval;
1295 /* make sure min and max are not equal */
1296 if (AlmostEqual2sComplement(im->minval, im->maxval, 4)) {
1302 /* make sure min and max are not both zero */
1303 if (AlmostEqual2sComplement(im->maxval, 0, 4)) {
1312 /* identify the point where the first gridline, label ... gets placed */
1314 time_t find_first_time(
1315 time_t start, /* what is the initial time */
1316 enum tmt_en baseint, /* what is the basic interval */
1317 long basestep /* how many if these do we jump a time */
1322 localtime_r(&start, &tm);
1326 tm. tm_sec -= tm.tm_sec % basestep;
1331 tm. tm_min -= tm.tm_min % basestep;
1337 tm. tm_hour -= tm.tm_hour % basestep;
1341 /* we do NOT look at the basestep for this ... */
1348 /* we do NOT look at the basestep for this ... */
1352 tm. tm_mday -= tm.tm_wday - 1; /* -1 because we want the monday */
1354 if (tm.tm_wday == 0)
1355 tm. tm_mday -= 7; /* we want the *previous* monday */
1363 tm. tm_mon -= tm.tm_mon % basestep;
1374 tm.tm_year + 1900) %basestep;
1380 /* identify the point where the next gridline, label ... gets placed */
1381 time_t find_next_time(
1382 time_t current, /* what is the initial time */
1383 enum tmt_en baseint, /* what is the basic interval */
1384 long basestep /* how many if these do we jump a time */
1390 localtime_r(¤t, &tm);
1395 tm. tm_sec += basestep;
1399 tm. tm_min += basestep;
1403 tm. tm_hour += basestep;
1407 tm. tm_mday += basestep;
1411 tm. tm_mday += 7 * basestep;
1415 tm. tm_mon += basestep;
1419 tm. tm_year += basestep;
1421 madetime = mktime(&tm);
1422 } while (madetime == -1); /* this is necessary to skip impssible times
1423 like the daylight saving time skips */
1429 /* calculate values required for PRINT and GPRINT functions */
1434 long i, ii, validsteps;
1437 int graphelement = 0;
1440 double magfact = -1;
1445 /* wow initializing tmvdef is quite a task :-) */
1446 time_t now = time(NULL);
1448 localtime_r(&now, &tmvdef);
1449 for (i = 0; i < im->gdes_c; i++) {
1450 vidx = im->gdes[i].vidx;
1451 switch (im->gdes[i].gf) {
1454 /* PRINT and GPRINT can now print VDEF generated values.
1455 * There's no need to do any calculations on them as these
1456 * calculations were already made.
1458 if (im->gdes[vidx].gf == GF_VDEF) { /* simply use vals */
1459 printval = im->gdes[vidx].vf.val;
1460 localtime_r(&im->gdes[vidx].vf.when, &tmvdef);
1461 } else { /* need to calculate max,min,avg etcetera */
1462 max_ii = ((im->gdes[vidx].end - im->gdes[vidx].start)
1463 / im->gdes[vidx].step * im->gdes[vidx].ds_cnt);
1466 for (ii = im->gdes[vidx].ds;
1467 ii < max_ii; ii += im->gdes[vidx].ds_cnt) {
1468 if (!finite(im->gdes[vidx].data[ii]))
1470 if (isnan(printval)) {
1471 printval = im->gdes[vidx].data[ii];
1476 switch (im->gdes[i].cf) {
1480 case CF_DEVSEASONAL:
1484 printval += im->gdes[vidx].data[ii];
1487 printval = min(printval, im->gdes[vidx].data[ii]);
1491 printval = max(printval, im->gdes[vidx].data[ii]);
1494 printval = im->gdes[vidx].data[ii];
1497 if (im->gdes[i].cf == CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1498 if (validsteps > 1) {
1499 printval = (printval / validsteps);
1502 } /* prepare printval */
1504 if ((percent_s = strstr(im->gdes[i].format, "%S")) != NULL) {
1505 /* Magfact is set to -1 upon entry to print_calc. If it
1506 * is still less than 0, then we need to run auto_scale.
1507 * Otherwise, put the value into the correct units. If
1508 * the value is 0, then do not set the symbol or magnification
1509 * so next the calculation will be performed again. */
1510 if (magfact < 0.0) {
1511 auto_scale(im, &printval, &si_symb, &magfact);
1512 if (printval == 0.0)
1515 printval /= magfact;
1517 *(++percent_s) = 's';
1518 } else if (strstr(im->gdes[i].format, "%s") != NULL) {
1519 auto_scale(im, &printval, &si_symb, &magfact);
1522 if (im->gdes[i].gf == GF_PRINT) {
1525 if (im->gdes[i].strftm) {
1526 prline.u_str = malloc((FMT_LEG_LEN + 2) * sizeof(char));
1527 strftime(prline.u_str,
1528 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1529 } else if (bad_format(im->gdes[i].format)) {
1531 ("bad format for PRINT in '%s'", im->gdes[i].format);
1535 sprintf_alloc(im->gdes[i].format, printval, si_symb);
1539 ("print[%ld]", prline_cnt++), RD_I_STR, prline);
1544 if (im->gdes[i].strftm) {
1545 strftime(im->gdes[i].legend,
1546 FMT_LEG_LEN, im->gdes[i].format, &tmvdef);
1548 if (bad_format(im->gdes[i].format)) {
1550 ("bad format for GPRINT in '%s'",
1551 im->gdes[i].format);
1554 #ifdef HAVE_SNPRINTF
1555 snprintf(im->gdes[i].legend,
1557 im->gdes[i].format, printval, si_symb);
1559 sprintf(im->gdes[i].legend,
1560 im->gdes[i].format, printval, si_symb);
1572 if (isnan(im->gdes[i].yrule)) { /* we must set this here or the legend printer can not decide to print the legend */
1573 im->gdes[i].yrule = im->gdes[vidx].vf.val;
1578 if (im->gdes[i].xrule == 0) { /* again ... the legend printer needs it */
1579 im->gdes[i].xrule = im->gdes[vidx].vf.when;
1588 #ifdef WITH_PIECHART
1596 ("STACK should already be turned into LINE or AREA here");
1601 return graphelement;
1605 /* place legends with color spots */
1611 int interleg = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1612 int border = im->text_prop[TEXT_PROP_LEGEND].size * 2.0;
1613 int fill = 0, fill_last;
1615 int leg_x = border, leg_y = im->yimg;
1616 int leg_y_prev = im->yimg;
1619 int i, ii, mark = 0;
1620 char prt_fctn; /*special printfunctions */
1621 char default_txtalign = TXA_JUSTIFIED; /*default line orientation */
1624 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
1625 if ((legspace = malloc(im->gdes_c * sizeof(int))) == NULL) {
1626 rrd_set_error("malloc for legspace");
1630 if (im->extra_flags & FULL_SIZE_MODE)
1631 leg_y = leg_y_prev =
1632 leg_y - (int) (im->text_prop[TEXT_PROP_LEGEND].size * 1.8);
1633 for (i = 0; i < im->gdes_c; i++) {
1635 /* hide legends for rules which are not displayed */
1636 if (im->gdes[i].gf == GF_TEXTALIGN) {
1637 default_txtalign = im->gdes[i].txtalign;
1640 if (!(im->extra_flags & FORCE_RULES_LEGEND)) {
1641 if (im->gdes[i].gf == GF_HRULE
1642 && (im->gdes[i].yrule <
1643 im->minval || im->gdes[i].yrule > im->maxval))
1644 im->gdes[i].legend[0] = '\0';
1645 if (im->gdes[i].gf == GF_VRULE
1646 && (im->gdes[i].xrule <
1647 im->start || im->gdes[i].xrule > im->end))
1648 im->gdes[i].legend[0] = '\0';
1651 leg_cc = strlen(im->gdes[i].legend);
1652 /* is there a controle code ant the end of the legend string ? */
1653 /* and it is not a tab \\t */
1655 && im->gdes[i].legend[leg_cc -
1657 && im->gdes[i].legend[leg_cc - 1] != 't') {
1658 prt_fctn = im->gdes[i].legend[leg_cc - 1];
1660 im->gdes[i].legend[leg_cc] = '\0';
1664 /* only valid control codes */
1665 if (prt_fctn != 'l' && prt_fctn != 'n' && /* a synonym for l */
1670 prt_fctn != 't' && prt_fctn != '\0' && prt_fctn != 'g') {
1673 ("Unknown control code at the end of '%s\\%c'",
1674 im->gdes[i].legend, prt_fctn);
1678 if (prt_fctn == 'n') {
1682 /* remove exess space from the end of the legend for \g */
1683 while (prt_fctn == 'g' &&
1684 leg_cc > 0 && im->gdes[i].legend[leg_cc - 1] == ' ') {
1686 im->gdes[i].legend[leg_cc] = '\0';
1691 /* no interleg space if string ends in \g */
1692 legspace[i] = (prt_fctn == 'g' ? 0 : interleg);
1694 fill += legspace[i];
1697 gfx_get_text_width(im,
1707 im->tabwidth, im->gdes[i].legend);
1712 /* who said there was a special tag ... ? */
1713 if (prt_fctn == 'g') {
1717 if (prt_fctn == '\0') {
1718 if (i == im->gdes_c - 1 || fill > im->ximg - 2 * border) {
1719 /* just one legend item is left right or center */
1720 switch (default_txtalign) {
1735 /* is it time to place the legends ? */
1736 if (fill > im->ximg - 2 * border) {
1744 if (leg_c == 1 && prt_fctn == 'j') {
1750 if (prt_fctn != '\0') {
1752 if (leg_c >= 2 && prt_fctn == 'j') {
1753 glue = (im->ximg - fill - 2 * border) / (leg_c - 1);
1757 if (prt_fctn == 'c')
1758 leg_x = (im->ximg - fill) / 2.0;
1759 if (prt_fctn == 'r')
1760 leg_x = im->ximg - fill - border;
1761 for (ii = mark; ii <= i; ii++) {
1762 if (im->gdes[ii].legend[0] == '\0')
1763 continue; /* skip empty legends */
1764 im->gdes[ii].leg_x = leg_x;
1765 im->gdes[ii].leg_y = leg_y;
1767 gfx_get_text_width(im, leg_x,
1776 im->tabwidth, im->gdes[ii].legend)
1781 if (im->extra_flags & FULL_SIZE_MODE) {
1782 /* only add y space if there was text on the line */
1783 if (leg_x > border || prt_fctn == 's')
1784 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1785 if (prt_fctn == 's')
1786 leg_y += im->text_prop[TEXT_PROP_LEGEND].size;
1788 if (leg_x > border || prt_fctn == 's')
1789 leg_y += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1790 if (prt_fctn == 's')
1791 leg_y -= im->text_prop[TEXT_PROP_LEGEND].size;
1799 if (im->extra_flags & FULL_SIZE_MODE) {
1800 if (leg_y != leg_y_prev) {
1801 *gY = leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1803 leg_y - im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1806 im->yimg = leg_y_prev;
1807 /* if we did place some legends we have to add vertical space */
1808 if (leg_y != im->yimg)
1809 im->yimg += im->text_prop[TEXT_PROP_LEGEND].size * 1.8;
1816 /* create a grid on the graph. it determines what to do
1817 from the values of xsize, start and end */
1819 /* the xaxis labels are determined from the number of seconds per pixel
1820 in the requested graph */
1824 int calc_horizontal_grid(
1832 int decimals, fractionals;
1834 im->ygrid_scale.labfact = 2;
1835 range = im->maxval - im->minval;
1836 scaledrange = range / im->magfact;
1837 /* does the scale of this graph make it impossible to put lines
1838 on it? If so, give up. */
1839 if (isnan(scaledrange)) {
1843 /* find grid spaceing */
1845 if (isnan(im->ygridstep)) {
1846 if (im->extra_flags & ALTYGRID) {
1847 /* find the value with max number of digits. Get number of digits */
1850 (max(fabs(im->maxval), fabs(im->minval)) *
1851 im->viewfactor / im->magfact));
1852 if (decimals <= 0) /* everything is small. make place for zero */
1854 im->ygrid_scale.gridstep =
1856 floor(log10(range * im->viewfactor / im->magfact))) /
1857 im->viewfactor * im->magfact;
1858 if (im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1859 im->ygrid_scale.gridstep = 0.1;
1860 /* should have at least 5 lines but no more then 15 */
1861 if (range / im->ygrid_scale.gridstep < 5)
1862 im->ygrid_scale.gridstep /= 10;
1863 if (range / im->ygrid_scale.gridstep > 15)
1864 im->ygrid_scale.gridstep *= 10;
1865 if (range / im->ygrid_scale.gridstep > 5) {
1866 im->ygrid_scale.labfact = 1;
1867 if (range / im->ygrid_scale.gridstep > 8)
1868 im->ygrid_scale.labfact = 2;
1870 im->ygrid_scale.gridstep /= 5;
1871 im->ygrid_scale.labfact = 5;
1875 (im->ygrid_scale.gridstep *
1876 (double) im->ygrid_scale.labfact * im->viewfactor /
1878 if (fractionals < 0) { /* small amplitude. */
1879 int len = decimals - fractionals + 1;
1881 if (im->unitslength < len + 2)
1882 im->unitslength = len + 2;
1883 sprintf(im->ygrid_scale.labfmt,
1885 -fractionals, (im->symbol != ' ' ? " %c" : ""));
1887 int len = decimals + 1;
1889 if (im->unitslength < len + 2)
1890 im->unitslength = len + 2;
1891 sprintf(im->ygrid_scale.labfmt,
1892 "%%%d.0f%s", len, (im->symbol != ' ' ? " %c" : ""));
1895 for (i = 0; ylab[i].grid > 0; i++) {
1896 pixel = im->ysize / (scaledrange / ylab[i].grid);
1902 for (i = 0; i < 4; i++) {
1903 if (pixel * ylab[gridind].lfac[i] >=
1904 2.5 * im->text_prop[TEXT_PROP_AXIS].size) {
1905 im->ygrid_scale.labfact = ylab[gridind].lfac[i];
1910 im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1913 im->ygrid_scale.gridstep = im->ygridstep;
1914 im->ygrid_scale.labfact = im->ylabfact;
1919 int draw_horizontal_grid(
1925 char graph_label[100];
1927 double X0 = im->xorigin;
1928 double X1 = im->xorigin + im->xsize;
1929 int sgrid = (int) (im->minval / im->ygrid_scale.gridstep - 1);
1930 int egrid = (int) (im->maxval / im->ygrid_scale.gridstep + 1);
1934 im->ygrid_scale.gridstep /
1935 (double) im->magfact * (double) im->viewfactor;
1936 MaxY = scaledstep * (double) egrid;
1937 for (i = sgrid; i <= egrid; i++) {
1939 im->ygrid_scale.gridstep * i);
1941 im->ygrid_scale.gridstep * (i + 1));
1943 if (floor(Y0 + 0.5) >=
1944 im->yorigin - im->ysize && floor(Y0 + 0.5) <= im->yorigin) {
1945 /* Make sure at least 2 grid labels are shown, even if it doesn't agree
1946 with the chosen settings. Add a label if required by settings, or if
1947 there is only one label so far and the next grid line is out of bounds. */
1948 if (i % im->ygrid_scale.labfact == 0
1950 && (YN < im->yorigin - im->ysize || YN > im->yorigin))) {
1951 if (im->symbol == ' ') {
1952 if (im->extra_flags & ALTYGRID) {
1953 sprintf(graph_label,
1954 im->ygrid_scale.labfmt,
1955 scaledstep * (double) i);
1958 sprintf(graph_label, "%4.1f",
1959 scaledstep * (double) i);
1961 sprintf(graph_label, "%4.0f",
1962 scaledstep * (double) i);
1966 char sisym = (i == 0 ? ' ' : im->symbol);
1968 if (im->extra_flags & ALTYGRID) {
1969 sprintf(graph_label,
1970 im->ygrid_scale.labfmt,
1971 scaledstep * (double) i, sisym);
1974 sprintf(graph_label, "%4.1f %c",
1975 scaledstep * (double) i, sisym);
1977 sprintf(graph_label, "%4.0f %c",
1978 scaledstep * (double) i, sisym);
1986 text_prop[TEXT_PROP_AXIS].
1988 im->graph_col[GRC_FONT],
1990 text_prop[TEXT_PROP_AXIS].
1993 text_prop[TEXT_PROP_AXIS].
1994 size, im->tabwidth, 0.0,
1995 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
1996 gfx_line(im, X0 - 2, Y0, X0, Y0,
1997 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1998 gfx_line(im, X1, Y0, X1 + 2, Y0,
1999 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2000 gfx_dashed_line(im, X0 - 2, Y0,
2006 im->grid_dash_on, im->grid_dash_off);
2007 } else if (!(im->extra_flags & NOMINOR)) {
2010 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2011 gfx_line(im, X1, Y0, X1 + 2, Y0,
2012 GRIDWIDTH, im->graph_col[GRC_GRID]);
2013 gfx_dashed_line(im, X0 - 1, Y0,
2017 graph_col[GRC_GRID],
2018 im->grid_dash_on, im->grid_dash_off);
2025 /* this is frexp for base 10 */
2036 iexp = floor(log(fabs(x)) / log(10));
2037 mnt = x / pow(10.0, iexp);
2040 mnt = x / pow(10.0, iexp);
2047 /* logaritmic horizontal grid */
2048 int horizontal_log_grid(
2052 double yloglab[][10] = {
2054 1.0, 10., 0.0, 0.0, 0.0, 0.0, 0.0,
2056 1.0, 5.0, 10., 0.0, 0.0, 0.0, 0.0,
2058 1.0, 2.0, 5.0, 7.0, 10., 0.0, 0.0,
2075 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} /* last line */
2077 int i, j, val_exp, min_exp;
2078 double nex; /* number of decades in data */
2079 double logscale; /* scale in logarithmic space */
2080 int exfrac = 1; /* decade spacing */
2081 int mid = -1; /* row in yloglab for major grid */
2082 double mspac; /* smallest major grid spacing (pixels) */
2083 int flab; /* first value in yloglab to use */
2084 double value, tmp, pre_value;
2086 char graph_label[100];
2088 nex = log10(im->maxval / im->minval);
2089 logscale = im->ysize / nex;
2090 /* major spacing for data with high dynamic range */
2091 while (logscale * exfrac < 3 * im->text_prop[TEXT_PROP_LEGEND].size) {
2098 /* major spacing for less dynamic data */
2100 /* search best row in yloglab */
2102 for (i = 0; yloglab[mid][i + 1] < 10.0; i++);
2103 mspac = logscale * log10(10.0 / yloglab[mid][i]);
2106 2 * im->text_prop[TEXT_PROP_LEGEND].size && yloglab[mid][0] > 0);
2109 /* find first value in yloglab */
2111 yloglab[mid][flab] < 10
2112 && frexp10(im->minval, &tmp) > yloglab[mid][flab]; flab++);
2113 if (yloglab[mid][flab] == 10.0) {
2118 if (val_exp % exfrac)
2119 val_exp += abs(-val_exp % exfrac);
2121 X1 = im->xorigin + im->xsize;
2126 value = yloglab[mid][flab] * pow(10.0, val_exp);
2127 if (AlmostEqual2sComplement(value, pre_value, 4))
2128 break; /* it seems we are not converging */
2130 Y0 = ytr(im, value);
2131 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2133 /* major grid line */
2135 X0 - 2, Y0, X0, Y0, MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2136 gfx_line(im, X1, Y0, X1 + 2, Y0,
2137 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2138 gfx_dashed_line(im, X0 - 2, Y0,
2143 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2145 if (im->extra_flags & FORCE_UNITS_SI) {
2150 scale = floor(val_exp / 3.0);
2152 pvalue = pow(10.0, val_exp % 3);
2154 pvalue = pow(10.0, ((val_exp + 1) % 3) + 2);
2155 pvalue *= yloglab[mid][flab];
2156 if (((scale + si_symbcenter) < (int) sizeof(si_symbol))
2157 && ((scale + si_symbcenter) >= 0))
2158 symbol = si_symbol[scale + si_symbcenter];
2161 sprintf(graph_label, "%3.0f %c", pvalue, symbol);
2163 sprintf(graph_label, "%3.0e", value);
2167 text_prop[TEXT_PROP_AXIS].
2169 im->graph_col[GRC_FONT],
2171 text_prop[TEXT_PROP_AXIS].
2174 text_prop[TEXT_PROP_AXIS].
2175 size, im->tabwidth, 0.0,
2176 GFX_H_RIGHT, GFX_V_CENTER, graph_label);
2178 if (mid < 4 && exfrac == 1) {
2179 /* find first and last minor line behind current major line
2180 * i is the first line and j tha last */
2182 min_exp = val_exp - 1;
2183 for (i = 1; yloglab[mid][i] < 10.0; i++);
2184 i = yloglab[mid][i - 1] + 1;
2188 i = yloglab[mid][flab - 1] + 1;
2189 j = yloglab[mid][flab];
2192 /* draw minor lines below current major line */
2193 for (; i < j; i++) {
2195 value = i * pow(10.0, min_exp);
2196 if (value < im->minval)
2198 Y0 = ytr(im, value);
2199 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2204 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2205 gfx_line(im, X1, Y0, X1 + 2, Y0,
2206 GRIDWIDTH, im->graph_col[GRC_GRID]);
2207 gfx_dashed_line(im, X0 - 1, Y0,
2211 graph_col[GRC_GRID],
2212 im->grid_dash_on, im->grid_dash_off);
2214 } else if (exfrac > 1) {
2215 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2216 value = pow(10.0, i);
2217 if (value < im->minval)
2219 Y0 = ytr(im, value);
2220 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2225 X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2226 gfx_line(im, X1, Y0, X1 + 2, Y0,
2227 GRIDWIDTH, im->graph_col[GRC_GRID]);
2228 gfx_dashed_line(im, X0 - 1, Y0,
2232 graph_col[GRC_GRID],
2233 im->grid_dash_on, im->grid_dash_off);
2238 if (yloglab[mid][++flab] == 10.0) {
2244 /* draw minor lines after highest major line */
2245 if (mid < 4 && exfrac == 1) {
2246 /* find first and last minor line below current major line
2247 * i is the first line and j tha last */
2249 min_exp = val_exp - 1;
2250 for (i = 1; yloglab[mid][i] < 10.0; i++);
2251 i = yloglab[mid][i - 1] + 1;
2255 i = yloglab[mid][flab - 1] + 1;
2256 j = yloglab[mid][flab];
2259 /* draw minor lines below current major line */
2260 for (; i < j; i++) {
2262 value = i * pow(10.0, min_exp);
2263 if (value < im->minval)
2265 Y0 = ytr(im, value);
2266 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2270 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2271 gfx_line(im, X1, Y0, X1 + 2, Y0,
2272 GRIDWIDTH, im->graph_col[GRC_GRID]);
2273 gfx_dashed_line(im, X0 - 1, Y0,
2277 graph_col[GRC_GRID],
2278 im->grid_dash_on, im->grid_dash_off);
2281 /* fancy minor gridlines */
2282 else if (exfrac > 1) {
2283 for (i = val_exp - exfrac / 3 * 2; i < val_exp; i += exfrac / 3) {
2284 value = pow(10.0, i);
2285 if (value < im->minval)
2287 Y0 = ytr(im, value);
2288 if (floor(Y0 + 0.5) <= im->yorigin - im->ysize)
2292 X0 - 2, Y0, X0, Y0, GRIDWIDTH, im->graph_col[GRC_GRID]);
2293 gfx_line(im, X1, Y0, X1 + 2, Y0,
2294 GRIDWIDTH, im->graph_col[GRC_GRID]);
2295 gfx_dashed_line(im, X0 - 1, Y0,
2299 graph_col[GRC_GRID],
2300 im->grid_dash_on, im->grid_dash_off);
2311 int xlab_sel; /* which sort of label and grid ? */
2312 time_t ti, tilab, timajor;
2314 char graph_label[100];
2315 double X0, Y0, Y1; /* points for filled graph and more */
2318 /* the type of time grid is determined by finding
2319 the number of seconds per pixel in the graph */
2320 if (im->xlab_user.minsec == -1) {
2321 factor = (im->end - im->start) / im->xsize;
2323 while (xlab[xlab_sel + 1].minsec !=
2324 -1 && xlab[xlab_sel + 1].minsec <= factor) {
2326 } /* pick the last one */
2327 while (xlab[xlab_sel - 1].minsec ==
2328 xlab[xlab_sel].minsec
2329 && xlab[xlab_sel].length > (im->end - im->start)) {
2331 } /* go back to the smallest size */
2332 im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
2333 im->xlab_user.gridst = xlab[xlab_sel].gridst;
2334 im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
2335 im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
2336 im->xlab_user.labtm = xlab[xlab_sel].labtm;
2337 im->xlab_user.labst = xlab[xlab_sel].labst;
2338 im->xlab_user.precis = xlab[xlab_sel].precis;
2339 im->xlab_user.stst = xlab[xlab_sel].stst;
2342 /* y coords are the same for every line ... */
2344 Y1 = im->yorigin - im->ysize;
2345 /* paint the minor grid */
2346 if (!(im->extra_flags & NOMINOR)) {
2347 for (ti = find_first_time(im->start,
2355 find_first_time(im->start,
2362 find_next_time(ti, im->xlab_user.gridtm, im->xlab_user.gridst)
2364 /* are we inside the graph ? */
2365 if (ti < im->start || ti > im->end)
2367 while (timajor < ti) {
2368 timajor = find_next_time(timajor,
2371 mgridtm, im->xlab_user.mgridst);
2374 continue; /* skip as falls on major grid line */
2376 gfx_line(im, X0, Y1 - 2, X0, Y1,
2377 GRIDWIDTH, im->graph_col[GRC_GRID]);
2378 gfx_line(im, X0, Y0, X0, Y0 + 2,
2379 GRIDWIDTH, im->graph_col[GRC_GRID]);
2380 gfx_dashed_line(im, X0, Y0 + 1, X0,
2383 graph_col[GRC_GRID],
2384 im->grid_dash_on, im->grid_dash_off);
2388 /* paint the major grid */
2389 for (ti = find_first_time(im->start,
2397 ti = find_next_time(ti, im->xlab_user.mgridtm, im->xlab_user.mgridst)
2399 /* are we inside the graph ? */
2400 if (ti < im->start || ti > im->end)
2403 gfx_line(im, X0, Y1 - 2, X0, Y1,
2404 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2405 gfx_line(im, X0, Y0, X0, Y0 + 3,
2406 MGRIDWIDTH, im->graph_col[GRC_MGRID]);
2407 gfx_dashed_line(im, X0, Y0 + 3, X0,
2411 [GRC_MGRID], im->grid_dash_on, im->grid_dash_off);
2413 /* paint the labels below the graph */
2415 find_first_time(im->start -
2424 im->xlab_user.precis / 2;
2425 ti = find_next_time(ti, im->xlab_user.labtm, im->xlab_user.labst)
2427 tilab = ti + im->xlab_user.precis / 2; /* correct time for the label */
2428 /* are we inside the graph ? */
2429 if (tilab < im->start || tilab > im->end)
2432 localtime_r(&tilab, &tm);
2433 strftime(graph_label, 99, im->xlab_user.stst, &tm);
2435 # error "your libc has no strftime I guess we'll abort the exercise here."
2440 im->graph_col[GRC_FONT],
2442 text_prop[TEXT_PROP_AXIS].
2445 text_prop[TEXT_PROP_AXIS].
2446 size, im->tabwidth, 0.0,
2447 GFX_H_CENTER, GFX_V_TOP, graph_label);
2456 /* draw x and y axis */
2457 /* gfx_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
2458 im->xorigin+im->xsize,im->yorigin-im->ysize,
2459 GRIDWIDTH, im->graph_col[GRC_AXIS]);
2461 gfx_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
2462 im->xorigin+im->xsize,im->yorigin-im->ysize,
2463 GRIDWIDTH, im->graph_col[GRC_AXIS]); */
2465 gfx_line(im, im->xorigin - 4,
2467 im->xorigin + im->xsize +
2468 4, im->yorigin, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2469 gfx_line(im, im->xorigin,
2472 im->yorigin - im->ysize -
2473 4, MGRIDWIDTH, im->graph_col[GRC_AXIS]);
2474 /* arrow for X and Y axis direction */
2475 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 */
2476 im->graph_col[GRC_ARROW]);
2478 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 */
2479 im->graph_col[GRC_ARROW]);
2488 double X0, Y0; /* points for filled graph and more */
2489 struct gfx_color_t water_color;
2491 /* draw 3d border */
2492 gfx_new_area(im, 0, im->yimg,
2493 2, im->yimg - 2, 2, 2, im->graph_col[GRC_SHADEA]);
2494 gfx_add_point(im, im->ximg - 2, 2);
2495 gfx_add_point(im, im->ximg, 0);
2496 gfx_add_point(im, 0, 0);
2498 gfx_new_area(im, 2, im->yimg - 2,
2500 im->yimg - 2, im->ximg - 2, 2, im->graph_col[GRC_SHADEB]);
2501 gfx_add_point(im, im->ximg, 0);
2502 gfx_add_point(im, im->ximg, im->yimg);
2503 gfx_add_point(im, 0, im->yimg);
2505 if (im->draw_x_grid == 1)
2507 if (im->draw_y_grid == 1) {
2508 if (im->logarithmic) {
2509 res = horizontal_log_grid(im);
2511 res = draw_horizontal_grid(im);
2514 /* dont draw horizontal grid if there is no min and max val */
2516 char *nodata = "No Data found";
2518 gfx_text(im, im->ximg / 2,
2521 im->graph_col[GRC_FONT],
2523 text_prop[TEXT_PROP_AXIS].
2526 text_prop[TEXT_PROP_AXIS].
2527 size, im->tabwidth, 0.0,
2528 GFX_H_CENTER, GFX_V_CENTER, nodata);
2532 /* yaxis unit description */
2537 im->graph_col[GRC_FONT],
2539 text_prop[TEXT_PROP_UNIT].
2542 text_prop[TEXT_PROP_UNIT].
2544 RRDGRAPH_YLEGEND_ANGLE, GFX_H_CENTER, GFX_V_CENTER, im->ylegend);
2548 im->graph_col[GRC_FONT],
2550 text_prop[TEXT_PROP_TITLE].
2553 text_prop[TEXT_PROP_TITLE].
2554 size, im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP, im->title);
2555 /* rrdtool 'logo' */
2556 water_color = im->graph_col[GRC_FONT];
2557 water_color.alpha = 0.3;
2558 gfx_text(im, im->ximg - 4, 5,
2561 text_prop[TEXT_PROP_AXIS].
2562 font, 5.5, im->tabwidth,
2563 -90, GFX_H_LEFT, GFX_V_TOP, "RRDTOOL / TOBI OETIKER");
2564 /* graph watermark */
2565 if (im->watermark[0] != '\0') {
2567 im->ximg / 2, im->yimg - 6,
2570 text_prop[TEXT_PROP_AXIS].
2571 font, 5.5, im->tabwidth, 0,
2572 GFX_H_CENTER, GFX_V_BOTTOM, im->watermark);
2576 if (!(im->extra_flags & NOLEGEND) & !(im->extra_flags & ONLY_GRAPH)) {
2577 for (i = 0; i < im->gdes_c; i++) {
2578 if (im->gdes[i].legend[0] == '\0')
2580 /* im->gdes[i].leg_y is the bottom of the legend */
2581 X0 = im->gdes[i].leg_x;
2582 Y0 = im->gdes[i].leg_y;
2583 gfx_text(im, X0, Y0,
2584 im->graph_col[GRC_FONT],
2587 [TEXT_PROP_LEGEND].font,
2590 [TEXT_PROP_LEGEND].size,
2592 GFX_H_LEFT, GFX_V_BOTTOM, im->gdes[i].legend);
2593 /* The legend for GRAPH items starts with "M " to have
2594 enough space for the box */
2595 if (im->gdes[i].gf != GF_PRINT &&
2596 im->gdes[i].gf != GF_GPRINT && im->gdes[i].gf != GF_COMMENT) {
2600 boxH = gfx_get_text_width(im, 0,
2608 size, im->tabwidth, "o") * 1.2;
2610 /* shift the box up a bit */
2612 /* make sure transparent colors show up the same way as in the graph */
2615 X0, Y0, X0 + boxH, Y0, im->graph_col[GRC_BACK]);
2616 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2618 gfx_new_area(im, X0, Y0 - boxV, X0,
2619 Y0, X0 + boxH, Y0, im->gdes[i].col);
2620 gfx_add_point(im, X0 + boxH, Y0 - boxV);
2623 cairo_new_path(im->cr);
2624 cairo_set_line_width(im->cr, 1.0);
2627 gfx_line_fit(im, &X0, &Y0);
2628 gfx_line_fit(im, &X1, &Y1);
2629 cairo_move_to(im->cr, X0, Y0);
2630 cairo_line_to(im->cr, X1, Y0);
2631 cairo_line_to(im->cr, X1, Y1);
2632 cairo_line_to(im->cr, X0, Y1);
2633 cairo_close_path(im->cr);
2634 cairo_set_source_rgba(im->cr,
2646 blue, im->graph_col[GRC_FRAME].alpha);
2647 if (im->gdes[i].dash) {
2648 /* make box borders in legend dashed if the graph is dashed */
2652 cairo_set_dash(im->cr, dashes, 1, 0.0);
2654 cairo_stroke(im->cr);
2655 cairo_restore(im->cr);
2662 /*****************************************************
2663 * lazy check make sure we rely need to create this graph
2664 *****************************************************/
2671 struct stat imgstat;
2674 return 0; /* no lazy option */
2675 if (strlen(im->graphfile) == 0)
2676 return 0; /* inmemory option */
2677 if (stat(im->graphfile, &imgstat) != 0)
2678 return 0; /* can't stat */
2679 /* one pixel in the existing graph is more then what we would
2681 if (time(NULL) - imgstat.st_mtime > (im->end - im->start) / im->xsize)
2683 if ((fd = fopen(im->graphfile, "rb")) == NULL)
2684 return 0; /* the file does not exist */
2685 switch (im->imgformat) {
2687 size = PngSize(fd, &(im->ximg), &(im->yimg));
2697 int graph_size_location(
2702 /* The actual size of the image to draw is determined from
2703 ** several sources. The size given on the command line is
2704 ** the graph area but we need more as we have to draw labels
2705 ** and other things outside the graph area
2708 int Xvertical = 0, Ytitle =
2709 0, Xylabel = 0, Xmain = 0, Ymain =
2710 0, Yxlabel = 0, Xspacing = 15, Yspacing = 15, Ywatermark = 4;
2712 if (im->extra_flags & ONLY_GRAPH) {
2714 im->ximg = im->xsize;
2715 im->yimg = im->ysize;
2716 im->yorigin = im->ysize;
2721 /** +---+--------------------------------------------+
2722 ** | y |...............graph title..................|
2723 ** | +---+-------------------------------+--------+
2726 ** | i | a | | pie |
2727 ** | s | x | main graph area | chart |
2732 ** | l | b +-------------------------------+--------+
2733 ** | e | l | x axis labels | |
2734 ** +---+---+-------------------------------+--------+
2735 ** |....................legends.....................|
2736 ** +------------------------------------------------+
2738 ** +------------------------------------------------+
2741 if (im->ylegend[0] != '\0') {
2742 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2745 if (im->title[0] != '\0') {
2746 /* The title is placed "inbetween" two text lines so it
2747 ** automatically has some vertical spacing. The horizontal
2748 ** spacing is added here, on each side.
2750 /* if necessary, reduce the font size of the title until it fits the image width */
2751 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2755 if (im->draw_x_grid) {
2756 Yxlabel = im->text_prop[TEXT_PROP_AXIS].size * 2.5;
2758 if (im->draw_y_grid || im->forceleftspace) {
2760 gfx_get_text_width(im, 0,
2768 size, im->tabwidth, "0") * im->unitslength;
2772 if (im->extra_flags & FULL_SIZE_MODE) {
2773 /* The actual size of the image to draw has been determined by the user.
2774 ** The graph area is the space remaining after accounting for the legend,
2775 ** the watermark, the pie chart, the axis labels, and the title.
2778 im->ximg = im->xsize;
2779 im->yimg = im->ysize;
2780 im->yorigin = im->ysize;
2783 im->yorigin += Ytitle;
2784 /* Now calculate the total size. Insert some spacing where
2785 desired. im->xorigin and im->yorigin need to correspond
2786 with the lower left corner of the main graph area or, if
2787 this one is not set, the imaginary box surrounding the
2789 /* Initial size calculation for the main graph area */
2790 Xmain = im->ximg - (Xylabel + 2 * Xspacing);
2792 Xmain -= Xspacing; /* put space between main graph area and right edge */
2793 im->xorigin = Xspacing + Xylabel;
2794 /* the length of the title should not influence with width of the graph
2795 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2796 if (Xvertical) { /* unit description */
2798 im->xorigin += Xvertical;
2802 /* The vertical size of the image is known in advance. The main graph area
2803 ** (Ymain) and im->yorigin must be set according to the space requirements
2804 ** of the legend and the axis labels.
2806 if (im->extra_flags & NOLEGEND) {
2807 /* set dimensions correctly if using full size mode with no legend */
2810 im->text_prop[TEXT_PROP_AXIS].size * 2.5 - Yspacing;
2811 Ymain = im->yorigin;
2813 /* Determine where to place the legends onto the image.
2814 ** Set Ymain and adjust im->yorigin to match the space requirements.
2816 if (leg_place(im, &Ymain) == -1)
2821 /* remove title space *or* some padding above the graph from the main graph area */
2825 Ymain -= 1.5 * Yspacing;
2828 /* watermark doesn't seem to effect the vertical size of the main graph area, oh well! */
2829 if (im->watermark[0] != '\0') {
2830 Ymain -= Ywatermark;
2834 } else { /* dimension options -width and -height refer to the dimensions of the main graph area */
2836 /* The actual size of the image to draw is determined from
2837 ** several sources. The size given on the command line is
2838 ** the graph area but we need more as we have to draw labels
2839 ** and other things outside the graph area.
2842 if (im->ylegend[0] != '\0') {
2843 Xvertical = im->text_prop[TEXT_PROP_UNIT].size * 2;
2847 if (im->title[0] != '\0') {
2848 /* The title is placed "inbetween" two text lines so it
2849 ** automatically has some vertical spacing. The horizontal
2850 ** spacing is added here, on each side.
2852 /* don't care for the with of the title
2853 Xtitle = gfx_get_text_width(im->canvas, 0,
2854 im->text_prop[TEXT_PROP_TITLE].font,
2855 im->text_prop[TEXT_PROP_TITLE].size,
2857 im->title, 0) + 2*Xspacing; */
2858 Ytitle = im->text_prop[TEXT_PROP_TITLE].size * 2.6 + 10;
2865 /* Now calculate the total size. Insert some spacing where
2866 desired. im->xorigin and im->yorigin need to correspond
2867 with the lower left corner of the main graph area or, if
2868 this one is not set, the imaginary box surrounding the
2871 /* The legend width cannot yet be determined, as a result we
2872 ** have problems adjusting the image to it. For now, we just
2873 ** forget about it at all; the legend will have to fit in the
2874 ** size already allocated.
2876 im->ximg = Xylabel + Xmain + 2 * Xspacing;
2878 im->ximg += Xspacing;
2879 im->xorigin = Xspacing + Xylabel;
2880 /* the length of the title should not influence with width of the graph
2881 if (Xtitle > im->ximg) im->ximg = Xtitle; */
2882 if (Xvertical) { /* unit description */
2883 im->ximg += Xvertical;
2884 im->xorigin += Xvertical;
2887 /* The vertical size is interesting... we need to compare
2888 ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend, Ywatermark} with
2889 ** Yvertical however we need to know {Ytitle+Ymain+Yxlabel}
2890 ** in order to start even thinking about Ylegend or Ywatermark.
2892 ** Do it in three portions: First calculate the inner part,
2893 ** then do the legend, then adjust the total height of the img,
2894 ** adding space for a watermark if one exists;
2896 /* reserve space for main and/or pie */
2897 im->yimg = Ymain + Yxlabel;
2898 im->yorigin = im->yimg - Yxlabel;
2899 /* reserve space for the title *or* some padding above the graph */
2902 im->yorigin += Ytitle;
2904 im->yimg += 1.5 * Yspacing;
2905 im->yorigin += 1.5 * Yspacing;
2907 /* reserve space for padding below the graph */
2908 im->yimg += Yspacing;
2909 /* Determine where to place the legends onto the image.
2910 ** Adjust im->yimg to match the space requirements.
2912 if (leg_place(im, 0) == -1)
2914 if (im->watermark[0] != '\0') {
2915 im->yimg += Ywatermark;
2923 static cairo_status_t cairo_output(
2927 unsigned int length)
2929 image_desc_t *im = closure;
2931 im->rendered_image =
2932 realloc(im->rendered_image, im->rendered_image_size + length);
2933 if (im->rendered_image == NULL)
2934 return CAIRO_STATUS_WRITE_ERROR;
2935 memcpy(im->rendered_image + im->rendered_image_size, data, length);
2936 im->rendered_image_size += length;
2937 return CAIRO_STATUS_SUCCESS;
2940 /* draw that picture thing ... */
2945 int lazy = lazy_check(im);
2946 double areazero = 0.0;
2947 graph_desc_t *lastgdes = NULL;
2949 PangoFontMap *font_map = pango_cairo_font_map_get_default();
2951 /* if we are lazy and there is nothing to PRINT ... quit now */
2952 if (lazy && im->prt_c == 0)
2954 /* pull the data from the rrd files ... */
2955 if (data_fetch(im) == -1)
2957 /* evaluate VDEF and CDEF operations ... */
2958 if (data_calc(im) == -1)
2960 /* calculate and PRINT and GPRINT definitions. We have to do it at
2961 * this point because it will affect the length of the legends
2962 * if there are no graph elements we stop here ...
2963 * if we are lazy, try to quit ...
2968 if ((i == 0) || lazy)
2970 /**************************************************************
2971 *** Calculating sizes and locations became a bit confusing ***
2972 *** so I moved this into a separate function. ***
2973 **************************************************************/
2974 if (graph_size_location(im, i) == -1)
2977 info.u_cnt = im->xorigin;
2978 grinfo_push(im, sprintf_alloc("graph_left"), RD_I_CNT, info);
2979 info.u_cnt = im->yorigin - im->ysize;
2980 grinfo_push(im, sprintf_alloc("graph_top"), RD_I_CNT, info);
2981 info.u_cnt = im->xsize;
2982 grinfo_push(im, sprintf_alloc("graph_width"), RD_I_CNT, info);
2983 info.u_cnt = im->ysize;
2984 grinfo_push(im, sprintf_alloc("graph_height"), RD_I_CNT, info);
2985 info.u_cnt = im->ximg;
2986 grinfo_push(im, sprintf_alloc("image_width"), RD_I_CNT, info);
2987 info.u_cnt = im->yimg;
2988 grinfo_push(im, sprintf_alloc("image_height"), RD_I_CNT, info);
2990 /* get actual drawing data and find min and max values */
2991 if (data_proc(im) == -1)
2993 if (!im->logarithmic) {
2997 /* identify si magnitude Kilo, Mega Giga ? */
2998 if (!im->rigid && !im->logarithmic)
2999 expand_range(im); /* make sure the upper and lower limit are
3002 info.u_val = im->minval;
3003 grinfo_push(im, sprintf_alloc("value_min"), RD_I_VAL, info);
3004 info.u_val = im->maxval;
3005 grinfo_push(im, sprintf_alloc("value_max"), RD_I_VAL, info);
3007 if (!calc_horizontal_grid(im))
3012 apply_gridfit(im); */
3013 /* the actual graph is created by going through the individual
3014 graph elements and then drawing them */
3015 cairo_surface_destroy(im->surface);
3016 switch (im->imgformat) {
3019 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3020 im->ximg * im->zoom,
3021 im->yimg * im->zoom);
3025 im->surface = strlen(im->graphfile)
3026 ? cairo_pdf_surface_create(im->graphfile, im->ximg * im->zoom,
3027 im->yimg * im->zoom)
3028 : cairo_pdf_surface_create_for_stream
3029 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3033 im->surface = strlen(im->graphfile)
3035 cairo_ps_surface_create(im->graphfile, im->ximg * im->zoom,
3036 im->yimg * im->zoom)
3037 : cairo_ps_surface_create_for_stream
3038 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3042 im->surface = strlen(im->graphfile)
3044 cairo_svg_surface_create(im->
3046 im->ximg * im->zoom, im->yimg * im->zoom)
3047 : cairo_svg_surface_create_for_stream
3048 (&cairo_output, im, im->ximg * im->zoom, im->yimg * im->zoom);
3049 cairo_svg_surface_restrict_to_version
3050 (im->surface, CAIRO_SVG_VERSION_1_1);
3053 im->cr = cairo_create(im->surface);
3054 cairo_set_antialias(im->cr, im->graph_antialias);
3055 cairo_scale(im->cr, im->zoom, im->zoom);
3056 pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(font_map), 100);
3057 gfx_new_area(im, 0, 0, 0, im->yimg,
3058 im->ximg, im->yimg, im->graph_col[GRC_BACK]);
3059 gfx_add_point(im, im->ximg, 0);
3061 gfx_new_area(im, im->xorigin,
3064 im->xsize, im->yorigin,
3067 im->yorigin - im->ysize, im->graph_col[GRC_CANVAS]);
3068 gfx_add_point(im, im->xorigin, im->yorigin - im->ysize);
3070 if (im->minval > 0.0)
3071 areazero = im->minval;
3072 if (im->maxval < 0.0)
3073 areazero = im->maxval;
3074 for (i = 0; i < im->gdes_c; i++) {
3075 switch (im->gdes[i].gf) {
3089 for (ii = 0; ii < im->xsize; ii++) {
3090 if (!isnan(im->gdes[i].p_data[ii])
3091 && im->gdes[i].p_data[ii] != 0.0) {
3092 if (im->gdes[i].yrule > 0) {
3099 im->ysize, 1.0, im->gdes[i].col);
3100 } else if (im->gdes[i].yrule < 0) {
3103 im->yorigin - im->ysize,
3108 im->ysize, 1.0, im->gdes[i].col);
3115 /* fix data points at oo and -oo */
3116 for (ii = 0; ii < im->xsize; ii++) {
3117 if (isinf(im->gdes[i].p_data[ii])) {
3118 if (im->gdes[i].p_data[ii] > 0) {
3119 im->gdes[i].p_data[ii] = im->maxval;
3121 im->gdes[i].p_data[ii] = im->minval;
3127 /* *******************************************************
3132 -------|--t-1--t--------------------------------
3134 if we know the value at time t was a then
3135 we draw a square from t-1 to t with the value a.
3137 ********************************************************* */
3138 if (im->gdes[i].col.alpha != 0.0) {
3139 /* GF_LINE and friend */
3140 if (im->gdes[i].gf == GF_LINE) {
3141 double last_y = 0.0;
3145 cairo_new_path(im->cr);
3146 cairo_set_line_width(im->cr, im->gdes[i].linewidth);
3147 if (im->gdes[i].dash) {
3148 cairo_set_dash(im->cr,
3149 im->gdes[i].p_dashes,
3150 im->gdes[i].ndash, im->gdes[i].offset);
3153 for (ii = 1; ii < im->xsize; ii++) {
3154 if (isnan(im->gdes[i].p_data[ii])
3155 || (im->slopemode == 1
3156 && isnan(im->gdes[i].p_data[ii - 1]))) {
3161 last_y = ytr(im, im->gdes[i].p_data[ii]);
3162 if (im->slopemode == 0) {
3163 double x = ii - 1 + im->xorigin;
3166 gfx_line_fit(im, &x, &y);
3167 cairo_move_to(im->cr, x, y);
3168 x = ii + im->xorigin;
3170 gfx_line_fit(im, &x, &y);
3171 cairo_line_to(im->cr, x, y);
3173 double x = ii - 1 + im->xorigin;
3175 ytr(im, im->gdes[i].p_data[ii - 1]);
3176 gfx_line_fit(im, &x, &y);
3177 cairo_move_to(im->cr, x, y);
3178 x = ii + im->xorigin;
3180 gfx_line_fit(im, &x, &y);
3181 cairo_line_to(im->cr, x, y);
3185 double x1 = ii + im->xorigin;
3186 double y1 = ytr(im, im->gdes[i].p_data[ii]);
3188 if (im->slopemode == 0
3189 && !AlmostEqual2sComplement(y1, last_y, 4)) {
3190 double x = ii - 1 + im->xorigin;
3193 gfx_line_fit(im, &x, &y);
3194 cairo_line_to(im->cr, x, y);
3197 gfx_line_fit(im, &x1, &y1);
3198 cairo_line_to(im->cr, x1, y1);
3201 cairo_set_source_rgba(im->cr,
3207 col.blue, im->gdes[i].col.alpha);
3208 cairo_set_line_cap(im->cr, CAIRO_LINE_CAP_ROUND);
3209 cairo_set_line_join(im->cr, CAIRO_LINE_JOIN_ROUND);
3210 cairo_stroke(im->cr);
3211 cairo_restore(im->cr);
3215 (double *) malloc(sizeof(double) * im->xsize * 2);
3217 (double *) malloc(sizeof(double) * im->xsize * 2);
3219 (double *) malloc(sizeof(double) * im->xsize * 2);
3221 (double *) malloc(sizeof(double) * im->xsize * 2);
3224 for (ii = 0; ii <= im->xsize; ii++) {
3227 if (idxI > 0 && (drawem != 0 || ii == im->xsize)) {
3233 AlmostEqual2sComplement(foreY
3237 AlmostEqual2sComplement(foreY
3247 foreY[cntI], im->gdes[i].col);
3248 while (cntI < idxI) {
3253 AlmostEqual2sComplement(foreY
3257 AlmostEqual2sComplement(foreY
3264 gfx_add_point(im, foreX[cntI], foreY[cntI]);
3266 gfx_add_point(im, backX[idxI], backY[idxI]);
3272 AlmostEqual2sComplement(backY
3276 AlmostEqual2sComplement(backY
3283 gfx_add_point(im, backX[idxI], backY[idxI]);
3293 if (ii == im->xsize)
3295 if (im->slopemode == 0 && ii == 0) {
3298 if (isnan(im->gdes[i].p_data[ii])) {
3302 ytop = ytr(im, im->gdes[i].p_data[ii]);
3303 if (lastgdes && im->gdes[i].stack) {
3304 ybase = ytr(im, lastgdes->p_data[ii]);
3306 ybase = ytr(im, areazero);
3308 if (ybase == ytop) {
3314 double extra = ytop;
3319 if (im->slopemode == 0) {
3320 backY[++idxI] = ybase - 0.2;
3321 backX[idxI] = ii + im->xorigin - 1;
3322 foreY[idxI] = ytop + 0.2;
3323 foreX[idxI] = ii + im->xorigin - 1;
3325 backY[++idxI] = ybase - 0.2;
3326 backX[idxI] = ii + im->xorigin;
3327 foreY[idxI] = ytop + 0.2;
3328 foreX[idxI] = ii + im->xorigin;
3330 /* close up any remaining area */
3335 } /* else GF_LINE */
3337 /* if color != 0x0 */
3338 /* make sure we do not run into trouble when stacking on NaN */
3339 for (ii = 0; ii < im->xsize; ii++) {
3340 if (isnan(im->gdes[i].p_data[ii])) {
3341 if (lastgdes && (im->gdes[i].stack)) {
3342 im->gdes[i].p_data[ii] = lastgdes->p_data[ii];
3344 im->gdes[i].p_data[ii] = areazero;
3348 lastgdes = &(im->gdes[i]);
3352 ("STACK should already be turned into LINE or AREA here");
3358 /* grid_paint also does the text */
3359 if (!(im->extra_flags & ONLY_GRAPH))
3361 if (!(im->extra_flags & ONLY_GRAPH))
3363 /* the RULES are the last thing to paint ... */
3364 for (i = 0; i < im->gdes_c; i++) {
3366 switch (im->gdes[i].gf) {
3368 if (im->gdes[i].yrule >= im->minval
3369 && im->gdes[i].yrule <= im->maxval) {
3371 if (im->gdes[i].dash) {
3372 cairo_set_dash(im->cr,
3373 im->gdes[i].p_dashes,
3374 im->gdes[i].ndash, im->gdes[i].offset);
3376 gfx_line(im, im->xorigin,
3377 ytr(im, im->gdes[i].yrule),
3378 im->xorigin + im->xsize,
3379 ytr(im, im->gdes[i].yrule), 1.0, im->gdes[i].col);
3380 cairo_stroke(im->cr);
3381 cairo_restore(im->cr);
3385 if (im->gdes[i].xrule >= im->start
3386 && im->gdes[i].xrule <= im->end) {
3388 if (im->gdes[i].dash) {
3389 cairo_set_dash(im->cr,
3390 im->gdes[i].p_dashes,
3391 im->gdes[i].ndash, im->gdes[i].offset);
3394 xtr(im, im->gdes[i].xrule),
3395 im->yorigin, xtr(im,
3399 im->yorigin - im->ysize, 1.0, im->gdes[i].col);
3400 cairo_stroke(im->cr);
3401 cairo_restore(im->cr);
3410 switch (im->imgformat) {
3413 cairo_status_t status;
3415 status = strlen(im->graphfile) ?
3416 cairo_surface_write_to_png(im->surface, im->graphfile)
3417 : cairo_surface_write_to_png_stream(im->surface, &cairo_output,
3420 if (status != CAIRO_STATUS_SUCCESS) {
3421 rrd_set_error("Could not save png to '%s'", im->graphfile);
3427 if (strlen(im->graphfile)) {
3428 cairo_show_page(im->cr);
3430 cairo_surface_finish(im->surface);
3439 /*****************************************************
3441 *****************************************************/
3448 if ((im->gdes = (graph_desc_t *)
3449 rrd_realloc(im->gdes, (im->gdes_c)
3450 * sizeof(graph_desc_t))) == NULL) {
3451 rrd_set_error("realloc graph_descs");
3456 im->gdes[im->gdes_c - 1].step = im->step;
3457 im->gdes[im->gdes_c - 1].step_orig = im->step;
3458 im->gdes[im->gdes_c - 1].stack = 0;
3459 im->gdes[im->gdes_c - 1].linewidth = 0;
3460 im->gdes[im->gdes_c - 1].debug = 0;
3461 im->gdes[im->gdes_c - 1].start = im->start;
3462 im->gdes[im->gdes_c - 1].start_orig = im->start;
3463 im->gdes[im->gdes_c - 1].end = im->end;
3464 im->gdes[im->gdes_c - 1].end_orig = im->end;
3465 im->gdes[im->gdes_c - 1].vname[0] = '\0';
3466 im->gdes[im->gdes_c - 1].data = NULL;
3467 im->gdes[im->gdes_c - 1].ds_namv = NULL;
3468 im->gdes[im->gdes_c - 1].data_first = 0;
3469 im->gdes[im->gdes_c - 1].p_data = NULL;
3470 im->gdes[im->gdes_c - 1].rpnp = NULL;
3471 im->gdes[im->gdes_c - 1].p_dashes = NULL;
3472 im->gdes[im->gdes_c - 1].shift = 0.0;
3473 im->gdes[im->gdes_c - 1].dash = 0;
3474 im->gdes[im->gdes_c - 1].ndash = 0;
3475 im->gdes[im->gdes_c - 1].offset = 0;
3476 im->gdes[im->gdes_c - 1].col.red = 0.0;
3477 im->gdes[im->gdes_c - 1].col.green = 0.0;
3478 im->gdes[im->gdes_c - 1].col.blue = 0.0;
3479 im->gdes[im->gdes_c - 1].col.alpha = 0.0;
3480 im->gdes[im->gdes_c - 1].legend[0] = '\0';
3481 im->gdes[im->gdes_c - 1].format[0] = '\0';
3482 im->gdes[im->gdes_c - 1].strftm = 0;
3483 im->gdes[im->gdes_c - 1].rrd[0] = '\0';
3484 im->gdes[im->gdes_c - 1].ds = -1;
3485 im->gdes[im->gdes_c - 1].cf_reduce = CF_AVERAGE;
3486 im->gdes[im->gdes_c - 1].cf = CF_AVERAGE;
3487 im->gdes[im->gdes_c - 1].yrule = DNAN;
3488 im->gdes[im->gdes_c - 1].xrule = 0;
3492 /* copies input untill the first unescaped colon is found
3493 or until input ends. backslashes have to be escaped as well */
3495 const char *const input,
3501 for (inp = 0; inp < len && input[inp] != ':' && input[inp] != '\0'; inp++) {
3502 if (input[inp] == '\\'
3503 && input[inp + 1] != '\0'
3504 && (input[inp + 1] == '\\' || input[inp + 1] == ':')) {
3505 output[outp++] = input[++inp];
3507 output[outp++] = input[inp];
3510 output[outp] = '\0';
3514 /* Now just a wrapper around rrd_graph_v */
3526 info_t *grinfo = NULL;
3529 grinfo = rrd_graph_v(argc, argv);
3535 if (strcmp(walker->key, "image_info") == 0) {
3538 rrd_realloc((*prdata),
3539 (prlines + 1) * sizeof(char *))) == NULL) {
3540 rrd_set_error("realloc prdata");
3543 /* imginfo goes to position 0 in the prdata array */
3544 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3545 + 2) * sizeof(char));
3546 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3547 (*prdata)[prlines] = NULL;
3549 /* skip anything else */
3550 walker = walker->next;
3554 if (strcmp(walker->key, "image_width") == 0) {
3555 *xsize = walker->value.u_int;
3556 } else if (strcmp(walker->key, "image_height") == 0) {
3557 *ysize = walker->value.u_int;
3558 } else if (strcmp(walker->key, "value_min") == 0) {
3559 *ymin = walker->value.u_val;
3560 } else if (strcmp(walker->key, "value_max") == 0) {
3561 *ymax = walker->value.u_val;
3562 } else if (strncmp(walker->key, "print", 5) == 0) { /* keys are prdate[0..] */
3565 rrd_realloc((*prdata),
3566 (prlines + 1) * sizeof(char *))) == NULL) {
3567 rrd_set_error("realloc prdata");
3570 (*prdata)[prlines - 1] = malloc((strlen(walker->value.u_str)
3571 + 2) * sizeof(char));
3572 (*prdata)[prlines] = NULL;
3573 strcpy((*prdata)[prlines - 1], walker->value.u_str);
3574 } else if (strcmp(walker->key, "image") == 0) {
3575 fwrite(walker->value.u_blo.ptr, walker->value.u_blo.size, 1,
3576 (stream ? stream : stdout));
3578 /* skip anything else */
3579 walker = walker->next;
3586 /* Some surgery done on this function, it became ridiculously big.
3588 ** - initializing now in rrd_graph_init()
3589 ** - options parsing now in rrd_graph_options()
3590 ** - script parsing now in rrd_graph_script()
3592 info_t *rrd_graph_v(
3599 rrd_graph_init(&im);
3600 /* a dummy surface so that we can measure text sizes for placements */
3601 im.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10);
3602 im.cr = cairo_create(im.surface);
3603 rrd_graph_options(argc, argv, &im);
3604 if (rrd_test_error()) {
3605 info_free(im.grinfo);
3610 if (optind >= argc) {
3611 info_free(im.grinfo);
3613 rrd_set_error("missing filename");
3617 if (strlen(argv[optind]) >= MAXPATH) {
3618 rrd_set_error("filename (including path) too long");
3619 info_free(im.grinfo);
3624 strncpy(im.graphfile, argv[optind], MAXPATH - 1);
3625 im.graphfile[MAXPATH - 1] = '\0';
3627 if (strcmp(im.graphfile, "-") == 0) {
3628 im.graphfile[0] = '\0';
3631 rrd_graph_script(argc, argv, &im, 1);
3632 if (rrd_test_error()) {
3633 info_free(im.grinfo);
3638 /* Everything is now read and the actual work can start */
3640 if (graph_paint(&im) == -1) {
3641 info_free(im.grinfo);
3647 /* The image is generated and needs to be output.
3648 ** Also, if needed, print a line with information about the image.
3655 sprintf_alloc(im.imginfo,
3658 im.ximg), (long) (im.zoom * im.yimg));
3659 grinfo_push(&im, sprintf_alloc("image_info"), RD_I_STR, info);
3662 if (im.rendered_image) {
3665 img.u_blo.size = im.rendered_image_size;
3666 img.u_blo.ptr = im.rendered_image;
3667 grinfo_push(&im, sprintf_alloc("image"), RD_I_BLO, img);
3674 void rrd_graph_init(
3683 #ifdef HAVE_SETLOCALE
3684 setlocale(LC_TIME, "");
3685 #ifdef HAVE_MBSTOWCS
3686 setlocale(LC_CTYPE, "");
3691 im->draw_x_grid = 1;
3692 im->draw_y_grid = 1;
3693 im->extra_flags = 0;
3694 im->font_options = cairo_font_options_create();
3695 im->forceleftspace = 0;
3698 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
3699 im->grid_dash_off = 1;
3700 im->grid_dash_on = 1;
3702 im->grinfo = (info_t *) NULL;
3703 im->grinfo_current = (info_t *) NULL;
3704 im->imgformat = IF_PNG;
3707 im->logarithmic = 0;
3713 im->rendered_image_size = 0;
3714 im->rendered_image = NULL;
3719 im->tabwidth = 40.0;
3720 im->title[0] = '\0';
3721 im->unitsexponent = 9999;
3722 im->unitslength = 6;
3723 im->viewfactor = 1.0;
3724 im->watermark[0] = '\0';
3726 im->xlab_user.minsec = -1;
3729 im->ygridstep = DNAN;
3731 im->ylegend[0] = '\0';
3735 cairo_font_options_set_hint_style
3736 (im->font_options, CAIRO_HINT_STYLE_FULL);
3737 cairo_font_options_set_hint_metrics
3738 (im->font_options, CAIRO_HINT_METRICS_ON);
3739 cairo_font_options_set_antialias(im->font_options, CAIRO_ANTIALIAS_GRAY);
3740 for (i = 0; i < DIM(graph_col); i++)
3741 im->graph_col[i] = graph_col[i];
3742 #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)
3745 char rrd_win_default_font[1000];
3747 windir = getenv("windir");
3748 /* %windir% is something like D:\windows or C:\winnt */
3749 if (windir != NULL) {
3750 strncpy(rrd_win_default_font, windir, 500);
3751 rrd_win_default_font[500] = '\0';
3752 strcat(rrd_win_default_font, "\\fonts\\");
3753 strcat(rrd_win_default_font, RRD_DEFAULT_FONT);
3754 for (i = 0; i < DIM(text_prop); i++) {
3755 strncpy(text_prop[i].font,
3756 rrd_win_default_font, sizeof(text_prop[i].font) - 1);
3757 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3765 deffont = getenv("RRD_DEFAULT_FONT");
3766 if (deffont != NULL) {
3767 for (i = 0; i < DIM(text_prop); i++) {
3768 strncpy(text_prop[i].font, deffont,
3769 sizeof(text_prop[i].font) - 1);
3770 text_prop[i].font[sizeof(text_prop[i].font) - 1] = '\0';
3774 for (i = 0; i < DIM(text_prop); i++) {
3775 im->text_prop[i].size = text_prop[i].size;
3776 strcpy(im->text_prop[i].font, text_prop[i].font);
3780 void rrd_graph_options(
3787 char *parsetime_error = NULL;
3788 char scan_gtm[12], scan_mtm[12], scan_ltm[12], col_nam[12];
3789 time_t start_tmp = 0, end_tmp = 0;
3791 struct rrd_time_value start_tv, end_tv;
3792 long unsigned int color;
3793 char *old_locale = "";
3795 /* defines for long options without a short equivalent. should be bytes,
3796 and may not collide with (the ASCII value of) short options */
3797 #define LONGOPT_UNITS_SI 255
3798 struct option long_options[] = {
3800 "start", required_argument, 0, 's'}, {
3801 "end", required_argument, 0,
3804 required_argument, 0,
3818 "height", required_argument, 0, 'h'}, {
3819 "full-size-mode", no_argument,
3837 "base", required_argument, 0, 'b'}, {
3838 "logarithmic", no_argument, 0,
3841 required_argument, 0,
3850 "imginfo", required_argument, 0, 'f'}, {
3852 required_argument, 0, 'a'}, {
3858 "zoom", required_argument, 0, 'm'}, {
3859 "no-legend", no_argument, 0,
3861 "force-rules-legend",
3871 "no-minor", no_argument, 0, 'I'}, {
3872 "slope-mode", no_argument, 0,
3875 no_argument, 0, 'A'}, {
3876 "alt-autoscale-min",
3880 "alt-autoscale-max",
3889 "units-exponent", required_argument,
3891 "units-length", required_argument,
3893 "units", required_argument, 0,
3894 LONGOPT_UNITS_SI}, {
3895 "step", required_argument, 0,
3898 required_argument, 0,
3903 "graph-render-mode",
3908 "font-smoothing-threshold",
3909 required_argument, 0, 'B'}, {
3910 "watermark", required_argument, 0, 'W'}, {
3911 "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 */
3916 opterr = 0; /* initialize getopt */
3917 parsetime("end-24h", &start_tv);
3918 parsetime("now", &end_tv);
3920 int option_index = 0;
3922 int col_start, col_end;
3924 opt = getopt_long(argc, argv,
3925 "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",
3926 long_options, &option_index);
3931 im->extra_flags |= NOMINOR;
3934 im->extra_flags |= ALTYGRID;
3937 im->extra_flags |= ALTAUTOSCALE;
3940 im->extra_flags |= ALTAUTOSCALE_MIN;
3943 im->extra_flags |= ALTAUTOSCALE_MAX;
3946 im->extra_flags |= ONLY_GRAPH;
3949 im->extra_flags |= NOLEGEND;
3952 im->extra_flags |= FORCE_RULES_LEGEND;
3954 case LONGOPT_UNITS_SI:
3955 if (im->extra_flags & FORCE_UNITS) {
3956 rrd_set_error("--units can only be used once!");
3957 setlocale(LC_NUMERIC, old_locale);
3960 if (strcmp(optarg, "si") == 0)
3961 im->extra_flags |= FORCE_UNITS_SI;
3963 rrd_set_error("invalid argument for --units: %s", optarg);
3968 im->unitsexponent = atoi(optarg);
3971 im->unitslength = atoi(optarg);
3972 im->forceleftspace = 1;
3975 old_locale = setlocale(LC_NUMERIC, "C");
3976 im->tabwidth = atof(optarg);
3977 setlocale(LC_NUMERIC, old_locale);
3980 old_locale = setlocale(LC_NUMERIC, "C");
3981 im->step = atoi(optarg);
3982 setlocale(LC_NUMERIC, old_locale);
3988 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3989 rrd_set_error("start time: %s", parsetime_error);
3994 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3995 rrd_set_error("end time: %s", parsetime_error);
4000 if (strcmp(optarg, "none") == 0) {
4001 im->draw_x_grid = 0;
4005 "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
4007 &im->xlab_user.gridst,
4009 &im->xlab_user.mgridst,
4011 &im->xlab_user.labst,
4012 &im->xlab_user.precis, &stroff) == 7 && stroff != 0) {
4013 strncpy(im->xlab_form, optarg + stroff,
4014 sizeof(im->xlab_form) - 1);
4015 im->xlab_form[sizeof(im->xlab_form) - 1] = '\0';
4017 (im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1) {
4018 rrd_set_error("unknown keyword %s", scan_gtm);
4021 (im->xlab_user.mgridtm = tmt_conv(scan_mtm))
4023 rrd_set_error("unknown keyword %s", scan_mtm);
4026 (im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1) {
4027 rrd_set_error("unknown keyword %s", scan_ltm);
4030 im->xlab_user.minsec = 1;
4031 im->xlab_user.stst = im->xlab_form;
4033 rrd_set_error("invalid x-grid format");
4039 if (strcmp(optarg, "none") == 0) {
4040 im->draw_y_grid = 0;
4043 old_locale = setlocale(LC_NUMERIC, "C");
4044 if (sscanf(optarg, "%lf:%d", &im->ygridstep, &im->ylabfact) == 2) {
4045 setlocale(LC_NUMERIC, old_locale);
4046 if (im->ygridstep <= 0) {
4047 rrd_set_error("grid step must be > 0");
4049 } else if (im->ylabfact < 1) {
4050 rrd_set_error("label factor must be > 0");
4054 setlocale(LC_NUMERIC, old_locale);
4055 rrd_set_error("invalid y-grid format");
4060 strncpy(im->ylegend, optarg, 150);
4061 im->ylegend[150] = '\0';
4064 old_locale = setlocale(LC_NUMERIC, "C");
4065 im->maxval = atof(optarg);
4066 setlocale(LC_NUMERIC, old_locale);
4069 old_locale = setlocale(LC_NUMERIC, "C");
4070 im->minval = atof(optarg);
4071 setlocale(LC_NUMERIC, old_locale);
4074 im->base = atol(optarg);
4075 if (im->base != 1024 && im->base != 1000) {
4077 ("the only sensible value for base apart from 1000 is 1024");
4082 long_tmp = atol(optarg);
4083 if (long_tmp < 10) {
4084 rrd_set_error("width below 10 pixels");
4087 im->xsize = long_tmp;
4090 long_tmp = atol(optarg);
4091 if (long_tmp < 10) {
4092 rrd_set_error("height below 10 pixels");
4095 im->ysize = long_tmp;
4098 im->extra_flags |= FULL_SIZE_MODE;
4101 /* interlaced png not supported at the moment */
4107 im->imginfo = optarg;
4111 (im->imgformat = if_conv(optarg)) == -1) {
4112 rrd_set_error("unsupported graphics format '%s'", optarg);
4123 im->logarithmic = 1;
4127 "%10[A-Z]#%n%8lx%n",
4128 col_nam, &col_start, &color, &col_end) == 2) {
4130 int col_len = col_end - col_start;
4135 (((color & 0xF00) * 0x110000) | ((color & 0x0F0) *
4143 (((color & 0xF000) *
4144 0x11000) | ((color & 0x0F00) *
4145 0x01100) | ((color &
4148 ((color & 0x000F) * 0x00011)
4152 color = (color << 8) + 0xff /* shift left by 8 */ ;
4157 rrd_set_error("the color format is #RRGGBB[AA]");
4160 if ((ci = grc_conv(col_nam)) != -1) {
4161 im->graph_col[ci] = gfx_hex_to_col(color);
4163 rrd_set_error("invalid color name '%s'", col_nam);
4167 rrd_set_error("invalid color def format");
4176 old_locale = setlocale(LC_NUMERIC, "C");
4177 if (sscanf(optarg, "%10[A-Z]:%lf%n", prop, &size, &end) >= 2) {
4178 int sindex, propidx;
4180 setlocale(LC_NUMERIC, old_locale);
4181 if ((sindex = text_prop_conv(prop)) != -1) {
4182 for (propidx = sindex;
4183 propidx < TEXT_PROP_LAST; propidx++) {
4185 im->text_prop[propidx].size = size;
4187 if ((int) strlen(prop) > end) {
4188 if (prop[end] == ':') {
4189 strncpy(im->text_prop[propidx].font,
4190 prop + end + 1, 255);
4191 im->text_prop[propidx].font[255] = '\0';
4194 ("expected after font size in '%s'",
4199 if (propidx == sindex && sindex != 0)
4203 rrd_set_error("invalid fonttag '%s'", prop);
4207 setlocale(LC_NUMERIC, old_locale);
4208 rrd_set_error("invalid text property format");
4214 old_locale = setlocale(LC_NUMERIC, "C");
4215 im->zoom = atof(optarg);
4216 setlocale(LC_NUMERIC, old_locale);
4217 if (im->zoom <= 0.0) {
4218 rrd_set_error("zoom factor must be > 0");
4223 strncpy(im->title, optarg, 150);
4224 im->title[150] = '\0';
4227 if (strcmp(optarg, "normal") == 0) {
4228 cairo_font_options_set_antialias
4229 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4230 cairo_font_options_set_hint_style
4231 (im->font_options, CAIRO_HINT_STYLE_FULL);
4232 } else if (strcmp(optarg, "light") == 0) {
4233 cairo_font_options_set_antialias
4234 (im->font_options, CAIRO_ANTIALIAS_GRAY);
4235 cairo_font_options_set_hint_style
4236 (im->font_options, CAIRO_HINT_STYLE_SLIGHT);
4237 } else if (strcmp(optarg, "mono") == 0) {
4238 cairo_font_options_set_antialias
4239 (im->font_options, CAIRO_ANTIALIAS_NONE);
4240 cairo_font_options_set_hint_style
4241 (im->font_options, CAIRO_HINT_STYLE_FULL);
4243 rrd_set_error("unknown font-render-mode '%s'", optarg);
4248 if (strcmp(optarg, "normal") == 0)
4249 im->graph_antialias = CAIRO_ANTIALIAS_GRAY;
4250 else if (strcmp(optarg, "mono") == 0)
4251 im->graph_antialias = CAIRO_ANTIALIAS_NONE;
4253 rrd_set_error("unknown graph-render-mode '%s'", optarg);
4258 /* not supported curently */
4261 strncpy(im->watermark, optarg, 100);
4262 im->watermark[99] = '\0';
4266 rrd_set_error("unknown option '%c'", optopt);
4268 rrd_set_error("unknown option '%s'", argv[optind - 1]);
4273 if (im->logarithmic && im->minval <= 0) {
4275 ("for a logarithmic yaxis you must specify a lower-limit > 0");
4279 if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
4280 /* error string is set in parsetime.c */
4284 if (start_tmp < 3600 * 24 * 365 * 10) {
4286 ("the first entry to fetch should be after 1980 (%ld)",
4291 if (end_tmp < start_tmp) {
4293 ("start (%ld) should be less than end (%ld)", start_tmp, end_tmp);
4297 im->start = start_tmp;
4299 im->step = max((long) im->step, (im->end - im->start) / im->xsize);
4302 int rrd_graph_color(
4310 graph_desc_t *gdp = &im->gdes[im->gdes_c - 1];
4312 color = strstr(var, "#");
4313 if (color == NULL) {
4314 if (optional == 0) {
4315 rrd_set_error("Found no color in %s", err);
4322 long unsigned int col;
4324 rest = strstr(color, ":");
4331 sscanf(color, "#%6lx%n", &col, &n);
4332 col = (col << 8) + 0xff /* shift left by 8 */ ;
4334 rrd_set_error("Color problem in %s", err);
4337 sscanf(color, "#%8lx%n", &col, &n);
4341 rrd_set_error("Color problem in %s", err);
4343 if (rrd_test_error())
4345 gdp->col = gfx_hex_to_col(col);
4358 while (*ptr != '\0')
4359 if (*ptr++ == '%') {
4361 /* line cannot end with percent char */
4364 /* '%s', '%S' and '%%' are allowed */
4365 if (*ptr == 's' || *ptr == 'S' || *ptr == '%')
4367 /* %c is allowed (but use only with vdef!) */
4368 else if (*ptr == 'c') {
4373 /* or else '% 6.2lf' and such are allowed */
4375 /* optional padding character */
4376 if (*ptr == ' ' || *ptr == '+' || *ptr == '-')
4378 /* This should take care of 'm.n' with all three optional */
4379 while (*ptr >= '0' && *ptr <= '9')
4383 while (*ptr >= '0' && *ptr <= '9')
4385 /* Either 'le', 'lf' or 'lg' must follow here */
4388 if (*ptr == 'e' || *ptr == 'f' || *ptr == 'g')
4403 const char *const str)
4405 /* A VDEF currently is either "func" or "param,func"
4406 * so the parsing is rather simple. Change if needed.
4414 old_locale = setlocale(LC_NUMERIC, "C");
4415 sscanf(str, "%le,%29[A-Z]%n", ¶m, func, &n);
4416 setlocale(LC_NUMERIC, old_locale);
4417 if (n == (int) strlen(str)) { /* matched */
4421 sscanf(str, "%29[A-Z]%n", func, &n);
4422 if (n == (int) strlen(str)) { /* matched */
4426 ("Unknown function string '%s' in VDEF '%s'",
4431 if (!strcmp("PERCENT", func))
4432 gdes->vf.op = VDEF_PERCENT;
4433 else if (!strcmp("MAXIMUM", func))
4434 gdes->vf.op = VDEF_MAXIMUM;
4435 else if (!strcmp("AVERAGE", func))
4436 gdes->vf.op = VDEF_AVERAGE;
4437 else if (!strcmp("STDEV", func))
4438 gdes->vf.op = VDEF_STDEV;
4439 else if (!strcmp("MINIMUM", func))
4440 gdes->vf.op = VDEF_MINIMUM;
4441 else if (!strcmp("TOTAL", func))
4442 gdes->vf.op = VDEF_TOTAL;
4443 else if (!strcmp("FIRST", func))
4444 gdes->vf.op = VDEF_FIRST;
4445 else if (!strcmp("LAST", func))
4446 gdes->vf.op = VDEF_LAST;
4447 else if (!strcmp("LSLSLOPE", func))
4448 gdes->vf.op = VDEF_LSLSLOPE;
4449 else if (!strcmp("LSLINT", func))
4450 gdes->vf.op = VDEF_LSLINT;
4451 else if (!strcmp("LSLCORREL", func))
4452 gdes->vf.op = VDEF_LSLCORREL;
4455 ("Unknown function '%s' in VDEF '%s'\n", func, gdes->vname);
4458 switch (gdes->vf.op) {
4460 if (isnan(param)) { /* no parameter given */
4462 ("Function '%s' needs parameter in VDEF '%s'\n",
4466 if (param >= 0.0 && param <= 100.0) {
4467 gdes->vf.param = param;
4468 gdes->vf.val = DNAN; /* undefined */
4469 gdes->vf.when = 0; /* undefined */
4472 ("Parameter '%f' out of range in VDEF '%s'\n",
4473 param, gdes->vname);
4486 case VDEF_LSLCORREL:
4488 gdes->vf.param = DNAN;
4489 gdes->vf.val = DNAN;
4493 ("Function '%s' needs no parameter in VDEF '%s'\n",
4507 graph_desc_t *src, *dst;
4511 dst = &im->gdes[gdi];
4512 src = &im->gdes[dst->vidx];
4513 data = src->data + src->ds;
4514 steps = (src->end - src->start) / src->step;
4517 ("DEBUG: start == %lu, end == %lu, %lu steps\n",
4518 src->start, src->end, steps);
4520 switch (dst->vf.op) {
4524 if ((array = malloc(steps * sizeof(double))) == NULL) {
4525 rrd_set_error("malloc VDEV_PERCENT");
4528 for (step = 0; step < steps; step++) {
4529 array[step] = data[step * src->ds_cnt];
4531 qsort(array, step, sizeof(double), vdef_percent_compar);
4532 field = (steps - 1) * dst->vf.param / 100;
4533 dst->vf.val = array[field];
4534 dst->vf.when = 0; /* no time component */
4537 for (step = 0; step < steps; step++)
4538 printf("DEBUG: %3li:%10.2f %c\n",
4539 step, array[step], step == field ? '*' : ' ');
4545 while (step != steps && isnan(data[step * src->ds_cnt]))
4547 if (step == steps) {
4551 dst->vf.val = data[step * src->ds_cnt];
4552 dst->vf.when = src->start + (step + 1) * src->step;
4554 while (step != steps) {
4555 if (finite(data[step * src->ds_cnt])) {
4556 if (data[step * src->ds_cnt] > dst->vf.val) {
4557 dst->vf.val = data[step * src->ds_cnt];
4558 dst->vf.when = src->start + (step + 1) * src->step;
4569 double average = 0.0;
4571 for (step = 0; step < steps; step++) {
4572 if (finite(data[step * src->ds_cnt])) {
4573 sum += data[step * src->ds_cnt];
4578 if (dst->vf.op == VDEF_TOTAL) {
4579 dst->vf.val = sum * src->step;
4580 dst->vf.when = 0; /* no time component */
4581 } else if (dst->vf.op == VDEF_AVERAGE) {
4582 dst->vf.val = sum / cnt;
4583 dst->vf.when = 0; /* no time component */
4585 average = sum / cnt;
4587 for (step = 0; step < steps; step++) {
4588 if (finite(data[step * src->ds_cnt])) {
4589 sum += pow((data[step * src->ds_cnt] - average), 2.0);
4592 dst->vf.val = pow(sum / cnt, 0.5);
4593 dst->vf.when = 0; /* no time component */
4603 while (step != steps && isnan(data[step * src->ds_cnt]))
4605 if (step == steps) {
4609 dst->vf.val = data[step * src->ds_cnt];
4610 dst->vf.when = src->start + (step + 1) * src->step;
4612 while (step != steps) {
4613 if (finite(data[step * src->ds_cnt])) {
4614 if (data[step * src->ds_cnt] < dst->vf.val) {
4615 dst->vf.val = data[step * src->ds_cnt];
4616 dst->vf.when = src->start + (step + 1) * src->step;
4623 /* The time value returned here is one step before the
4624 * actual time value. This is the start of the first
4628 while (step != steps && isnan(data[step * src->ds_cnt]))
4630 if (step == steps) { /* all entries were NaN */
4634 dst->vf.val = data[step * src->ds_cnt];
4635 dst->vf.when = src->start + step * src->step;
4639 /* The time value returned here is the
4640 * actual time value. This is the end of the last
4644 while (step >= 0 && isnan(data[step * src->ds_cnt]))
4646 if (step < 0) { /* all entries were NaN */
4650 dst->vf.val = data[step * src->ds_cnt];
4651 dst->vf.when = src->start + (step + 1) * src->step;
4656 case VDEF_LSLCORREL:{
4657 /* Bestfit line by linear least squares method */
4660 double SUMx, SUMy, SUMxy, SUMxx, SUMyy, slope, y_intercept, correl;
4667 for (step = 0; step < steps; step++) {
4668 if (finite(data[step * src->ds_cnt])) {
4671 SUMxx += step * step;
4672 SUMxy += step * data[step * src->ds_cnt];
4673 SUMy += data[step * src->ds_cnt];
4674 SUMyy += data[step * src->ds_cnt] * data[step * src->ds_cnt];
4678 slope = (SUMx * SUMy - cnt * SUMxy) / (SUMx * SUMx - cnt * SUMxx);
4679 y_intercept = (SUMy - slope * SUMx) / cnt;
4682 (SUMx * SUMy) / cnt) /
4684 (SUMx * SUMx) / cnt) * (SUMyy - (SUMy * SUMy) / cnt));
4686 if (dst->vf.op == VDEF_LSLSLOPE) {
4687 dst->vf.val = slope;
4689 } else if (dst->vf.op == VDEF_LSLINT) {
4690 dst->vf.val = y_intercept;
4692 } else if (dst->vf.op == VDEF_LSLCORREL) {
4693 dst->vf.val = correl;
4706 /* NaN < -INF < finite_values < INF */
4707 int vdef_percent_compar(
4713 /* Equality is not returned; this doesn't hurt except
4714 * (maybe) for a little performance.
4717 /* First catch NaN values. They are smallest */
4718 if (isnan(*(double *) a))
4720 if (isnan(*(double *) b))
4722 /* NaN doesn't reach this part so INF and -INF are extremes.
4723 * The sign from isinf() is compatible with the sign we return
4725 if (isinf(*(double *) a))
4726 return isinf(*(double *) a);
4727 if (isinf(*(double *) b))
4728 return isinf(*(double *) b);
4729 /* If we reach this, both values must be finite */
4730 if (*(double *) a < *(double *) b)
4739 enum info_type type,
4742 im->grinfo_current = info_push(im->grinfo_current, key, type, value);
4743 if (im->grinfo == NULL) {
4744 im->grinfo = im->grinfo_current;